Compare commits

...

42 Commits

Author SHA1 Message Date
Eric Fennis
797cf7667d Adjust styling 2025-12-04 12:21:24 +01:00
Eric Fennis
979e07fbf9 Add examples 2025-11-28 17:22:23 +01:00
Eric Fennis
2f5f78c1c2 Save this 2025-11-28 14:39:50 +01:00
Eric Fennis
634322e96c Get new editor 2025-11-28 12:54:51 +01:00
Eric Fennis
53ebc4c076 Add snackplayer 2025-11-28 11:06:09 +01:00
Eric Fennis
7d023209f0 Merge branch 'next' of https://github.com/lucide-icons/lucide into update-site 2025-11-27 16:09:32 +01:00
Eric Fennis
d58a2e43c6 Merge branch 'next' of https://github.com/lucide-icons/lucide into next 2025-11-27 10:58:43 +01:00
Eric Fennis
5ecf78bb8a Merge branch 'main' of https://github.com/lucide-icons/lucide into next 2025-11-27 10:58:34 +01:00
Eric Fennis
aa8f74eb9e fix(icons): Remove brand icons (#3639)
* Remove brand icons

* Apply feedback

* adjust schema
2025-11-27 10:56:32 +01:00
Eric Fennis
7327637532 Merge branch 'main' of https://github.com/lucide-icons/lucide into next 2025-11-27 10:56:13 +01:00
Eric Fennis
08bd4b33a0 Merge branch 'main' of https://github.com/lucide-icons/lucide into next 2025-11-21 14:58:50 +01:00
Eric Fennis
b23d4f110b Adjust examples preact docs 2025-11-19 23:24:08 +01:00
Eric Fennis
7d86d083bd Merge branch 'main' of https://github.com/lucide-icons/lucide into update-site 2025-11-14 15:48:57 +01:00
Eric Fennis
5c523be780 update preact docs 2025-11-04 17:35:43 +01:00
Eric Fennis
498b8390d7 Added solid docs 2025-10-27 21:27:42 +01:00
Eric Fennis
528de5284b Add group icons 2025-10-26 17:21:55 +01:00
Eric Fennis
a19fb21c5d remove spiral animation component 2025-10-26 17:19:29 +01:00
Eric Fennis
5770f62d02 Add solid pages 2025-10-26 17:14:13 +01:00
Eric Fennis
94e3f56166 Finish svelte docs 2025-10-24 15:18:25 +02:00
Eric Fennis
a432aeee69 Adjust home hero animation 2025-10-24 09:39:26 +02:00
Eric Fennis
b0ed2827f8 Adjust animation home 2025-10-21 21:11:58 +02:00
Eric Fennis
bfbd5d7bda Write docs for Vue 2025-10-20 20:25:34 +02:00
Eric Fennis
1090cbc810 adjust easing home animation 2025-10-20 19:15:12 +02:00
Eric Fennis
fa6e015520 fix framework select 2025-10-20 19:13:38 +02:00
Eric Fennis
1756617885 Fix layout accessibility page 2025-10-17 15:23:11 +02:00
Eric Fennis
f5d0a2a220 Add docs for vue types 2025-10-17 14:58:00 +02:00
Eric Fennis
b1675c4c33 chore(packages): Remove umd exports (#3641)
* Remove UMD export

* Revert package file change
2025-10-16 13:49:19 +02:00
Eric Fennis
5e27d7d11a Add scaledown animation 2025-10-15 23:01:05 +02:00
Eric Fennis
3a608bac58 Updates docs 2025-10-15 21:08:38 +02:00
Eric Fennis
bcebce5927 Adjust animation 2025-10-15 14:30:50 +02:00
Eric Fennis
84346a148e Some adjustments 2025-10-15 12:25:27 +02:00
Eric Fennis
fac9fa4efd Add animation? 2025-10-14 18:36:53 +02:00
Eric Fennis
cde1ebaa0b Fix dynamic sidebar 2025-10-14 15:29:55 +02:00
Eric Fennis
9f67c0943b Add svelte docs 2025-10-14 13:24:42 +02:00
Eric Fennis
999bca4575 Add basics section to vue pages 2025-10-12 00:01:29 +02:00
Eric Fennis
d7e7493bd6 Add typescript documentation 2025-10-10 13:01:50 +02:00
Eric Fennis
f34e889831 Add matrix hero 2025-10-10 09:38:55 +02:00
Eric Fennis
65403c9298 update accessebility 2025-09-24 12:18:48 +02:00
Eric Fennis
8eb64f4574 Adjust color 2025-09-20 22:54:48 +02:00
Eric Fennis
d1dd94a717 Update images 2025-09-19 15:04:51 +02:00
Eric Fennis
758fa4b75f Merge branch 'main' into next 2025-09-19 13:59:13 +02:00
Eric Fennis
7bbb1e1fea feat(@lucide/vue): Rename Vue package name to @lucide/vue (#3337)
* Remove old vue 2 package

* Add @lucide/vue package

* Remove old vue 2 doc

* Update docs

* Adjust export template

* Adjust vue package!

* Fix tests

* Format code

* Update packages/vue/src/Icon.ts

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Adjust vue package in docs

* Update deadlinks

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-11 14:38:34 +02:00
401 changed files with 10962 additions and 2611 deletions

View File

@@ -9,9 +9,3 @@ strikethrough
touchpad
ungroup
toc
# Brands
codepen
codesandbox
dribbble
x.com

43
.github/workflows/lucide-vue.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Lucide Vue checks
on:
pull_request:
paths:
- packages/lucide-vue/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- pnpm-lock.yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/vue build
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/vue test

View File

@@ -60,6 +60,7 @@ jobs:
'lucide-svelte',
'@lucide/astro',
'@lucide/svelte',
'@lucide/vue',
]
steps:
- uses: actions/checkout@v6

View File

@@ -2,5 +2,5 @@
"cSpell.words": ["devs", "preact", "Preact"],
"eslint.enable": true,
"eslint.validate": ["javascript", "svg"],
"svg.preview.background": "transparent"
"svg.preview.background": "editor"
}

View File

@@ -1,5 +0,0 @@
{
"$schema": "../category.schema.json",
"title": "Brands",
"icon": "facebook"
}

View File

@@ -1,6 +1,10 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress';
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
import container from 'markdown-it-container';
import { renderSandbox } from 'vitepress-plugin-sandpack';
import sidebar from './sidebar';
import snackPlayer from './plugins/snackPlayer';
const title = 'Lucide';
const socialTitle = 'Lucide Icons';
@@ -13,6 +17,19 @@ export default defineConfig({
cleanUrls: true,
outDir: '.vercel/output/static',
srcExclude: ['**/README.md'],
markdown: {
config(md) {
md.use(groupIconMdPlugin);
md.use(snackPlayer);
md.use(container, 'sandbox', {
render (tokens, idx) {
console.log(tokens);
return renderSandbox(tokens, idx, 'sandbox');
},
});
},
},
vite: {
resolve: {
alias: [
@@ -34,6 +51,9 @@ export default defineConfig({
},
],
},
plugins: [
groupIconVitePlugin()
],
},
head: [
[

View File

@@ -15,10 +15,6 @@
"name": "arrows",
"title": "Arrows"
},
{
"name": "brands",
"title": "Brands"
},
{
"name": "buildings",
"title": "Buildings"

View File

@@ -31,20 +31,12 @@
}
]
},
"lucide-vue-next": {
"@lucide/vue": {
"order": 2,
"icon": "vue-next",
"icon": "vue",
"docsAlias": "lucide-vue",
"packageDirname": "vue",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/lucide-vue-next",
"href": "https://www.npmjs.com/package/lucide-vue-next"
}
]
},
"lucide-svelte": {

View File

@@ -457,18 +457,7 @@ const SvgPreview = React.forwardRef<
<ColoredPath
paths={paths}
colors={[
'#1982c4',
'#4267AC',
'#6a4c93',
'#B55379',
'#FF595E',
'#FF7655',
'#ff924c',
'#FFAE43',
'#ffca3a',
'#C5CA30',
'#8ac926',
'#52A675',
'##dfdfd6',
]}
/>
<Radii

View File

@@ -37,7 +37,7 @@ export default App;
language: 'vue',
title: 'Vue',
code: `<script setup>
import { $PascalCase } from 'lucide-vue-next';
import { $PascalCase } from '@lucide/vue';
</script>
<template>

View File

@@ -0,0 +1,110 @@
/**
* SnackPlayer markdown-it plugin
*
* Converts fenced code blocks like:
*
* ```SnackPlayer name=My%20Example&description=Nice%20demo&ext=tsx
* // code here
* ```
*
* into:
*
* <div
* class="snack-player"
* data-snack-name="My Example"
* ...
* />
*/
import type MarkdownIt from 'markdown-it';
type SnackParams = Record<string, string>;
function parseParams(paramString = ''): SnackParams {
const params = Object.fromEntries(
new URLSearchParams(paramString),
) as SnackParams;
if (!params.platform) {
params.platform = 'web';
}
return params;
}
export default function snackPlayerPlugin(md: MarkdownIt) {
const escapeHtml = md.utils.escapeHtml;
const defaultFence =
md.renderer.rules.fence ||
((tokens, idx, options, env, self) =>
self.renderToken(tokens, idx, options));
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
const token = tokens[idx];
const info = (token.info || '').trim();
if (!info) {
return defaultFence(tokens, idx, options, env, self);
}
const [lang, ...rest] = info.split(/\s+/);
if (lang !== 'SnackPlayer') {
return defaultFence(tokens, idx, options, env, self);
}
const paramString = rest.join(' ');
const params = parseParams(paramString);
// Gather necessary params
const name = params.name
? decodeURIComponent(params.name)
: 'Example';
const description = params.description
? decodeURIComponent(params.description)
: 'Example usage';
const ext = params.ext ? decodeURIComponent(params.ext) : 'tsx';
const filename = `App.${ext}`;
const files = encodeURIComponent(
JSON.stringify({
[filename]: {
type: 'CODE',
contents: token.content,
},
}),
);
const dependencies =
'react-native-safe-area-context' +
(params.dependencies ? `,${params.dependencies}` : '');
const platform = params.platform ?? 'web';
const supportedPlatforms =
params.supportedPlatforms ?? 'ios,android,web';
const theme = params.theme ?? 'light';
const preview = params.preview ?? 'true';
const loading = params.loading ?? 'lazy';
const deviceAppearance = params.deviceAppearance ?? 'dark';
// Build the HTML output (escaping where appropriate)
return (
`<SnackPlayer` +
` class="snack-player"` +
` data-snack-name="${escapeHtml(name)}"` +
` data-snack-description="${escapeHtml(description)}"` +
` data-snack-files="${files}"` +
` data-snack-dependencies="${escapeHtml(dependencies)}"` +
` data-snack-platform="${escapeHtml(platform)}"` +
` data-snack-supported-platforms="${escapeHtml(
supportedPlatforms,
)}"` +
// ` data-snack-theme="${escapeHtml(theme)}"` +
` data-snack-preview="${escapeHtml(preview)}"` +
` data-snack-loading="${escapeHtml(loading)}"` +
` data-snack-device-appearance="${escapeHtml(
deviceAppearance,
)}"` +
` data-snack-device-frame="false"` +
`></SnackPlayer>`
);
};
}

View File

@@ -1,139 +0,0 @@
import { DefaultTheme, UserConfig } from 'vitepress';
const sidebar: UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'] = {
guide: [
{
text: 'Introduction',
items: [
{ text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' },
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
link: '/guide/basics/color',
},
{
text: 'Sizing',
link: '/guide/basics/sizing',
},
{
text: 'Stroke width',
link: '/guide/basics/stroke-width',
},
],
},
// TODO: Add this section
{
text: 'Advanced',
items: [
{
text: 'Accessibility',
link: '/guide/advanced/accessibility',
},
{
text: 'Global styling',
link: '/guide/advanced/global-styling',
},
// {
// text: 'Animations',
// },
{
text: 'Filled icons',
link: '/guide/advanced/filled-icons',
},
{
text: 'Aliased Names',
link: '/guide/advanced/aliased-names',
},
// {
// text: 'Combining icons',
// },
// {
// text: 'Dynamic imports'
// },
// {
// text: 'Auto importing'
// },
],
},
{
text: 'Packages',
items: [
{
text: 'Lucide',
link: '/guide/packages/lucide',
},
{
text: 'Lucide React',
link: '/guide/packages/lucide-react',
},
{
text: 'Lucide Vue',
link: '/guide/packages/lucide-vue-next',
},
{
text: 'Lucide Svelte',
link: '/guide/packages/lucide-svelte',
},
{
text: 'Lucide Solid',
link: '/guide/packages/lucide-solid',
},
{
text: 'Lucide React Native',
link: '/guide/packages/lucide-react-native',
},
{
text: 'Lucide Angular',
link: '/guide/packages/lucide-angular',
},
{
text: 'Lucide Preact',
link: '/guide/packages/lucide-preact',
},
{
text: 'Lucide Astro',
link: '/guide/packages/lucide-astro',
},
{
text: 'Lucide Static',
link: '/guide/packages/lucide-static',
},
],
},
{
text: 'Contributing',
items: [
{
text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide',
},
{
text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide',
},
{
text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide',
},
{
text: 'Designing in Figma',
link: '/guide/design/figma-guide',
},
{
text: 'Designing in Affinity Designer',
link: '/guide/design/affinity-designer-guide',
},
],
},
],
// This should be here to keep the sidebar shown on the icons page
icons: [{ text: '', link: '/' }],
};
export default sidebar;

View File

@@ -0,0 +1,66 @@
import { DefaultTheme, UserConfig } from 'vitepress';
import { reactSidebar } from './react';
import { vueSidebar } from './vue';
import { svelteSidebar } from './svelte';
import { lucideSidebar } from './lucide';
import { solidSidebar } from './solid';
import { preactSidebar } from './preact';
import { reactNativeSidebar } from './react-native';
type Sidebar = UserConfig<DefaultTheme.Config>['themeConfig']['sidebar']
export const guideSidebarTop: DefaultTheme.SidebarItem[] = [
{
text: 'Introduction',
items: [
{ text: 'What is lucide?', link: '/guide/' },
{ text: 'Installation', link: '/guide/installation' },
{ text: 'Comparison', link: '/guide/comparison' },
],
},
]
const sidebar: Sidebar = {
'/guide': [{ text: '', link: '/' }],
'/guide/lucide': lucideSidebar,
'/guide/react': reactSidebar,
'/guide/vue': vueSidebar,
'/guide/svelte': svelteSidebar,
'/guide/solid': solidSidebar,
'/guide/preact': preactSidebar,
'/guide/react-native/': reactNativeSidebar,
'/resources': [
{
text: "Community",
},
{
text: 'Designing icons',
items: [
{
text: 'Icon Design Principles',
link: '/guide/design/icon-design-guide',
},
{
text: 'Designing in Illustrator',
link: '/guide/design/illustrator-guide',
},
{
text: 'Designing in InkScape',
link: '/guide/design/inkscape-guide',
},
{
text: 'Designing in Figma',
link: '/guide/design/figma-guide',
},
{
text: 'Designing in Affinity Designer',
link: '/guide/design/affinity-designer-guide',
},
],
},
],
// This should be here to keep the sidebar shown on the icons page
icons: [{ text: '', link: '/' }],
};
export default sidebar;

View File

@@ -0,0 +1,68 @@
import { DefaultTheme } from "vitepress";
export const lucideSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/lucide/',
},
{
text: 'Getting started',
link: '/guide/lucide/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/lucide/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/lucide/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/lucide/basics/stroke-width',
},
],
},
// TODO: Add this section
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/lucide/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'With shadow DOM',
link: '/guide/lucide/advanced/shadow-dom',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/lucide/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'With lucide lab',
link: '/guide/lucide/advanced/with-lucide-lab',
desc: 'Using lucide-lab with lucide',
},
{
text: 'Filled icons',
link: '/guide/lucide/advanced/filled-icons',
desc: 'Using filled icons in lucide',
},
],
},
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,97 @@
import { DefaultTheme } from "vitepress";
export const preactSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/preact/',
},
{
text: 'Getting started',
link: '/guide/preact/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/preact/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/preact/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/preact/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/preact/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/preact/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/preact/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/preact/advanced/with-lucide-lab',
desc: 'Using lucide-lab with lucide-preact',
},
// {
// text: 'Animations',
// link: '/guide/preact/advanced/animations',
// desc: 'Add animations to your icons',
// },
{
text: 'Filled icons',
link: '/guide/preact/advanced/filled-icons',
desc: 'Using filled icons in lucide-preact',
},
{
text: 'Aliased Names',
link: '/guide/preact/advanced/aliased-names',
desc: 'Using aliased icon names',
},
{
text: 'Combining icons',
link: '/guide/preact/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,82 @@
import { DefaultTheme } from "vitepress";
export const reactNativeSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/react-native/',
},
{
text: 'Getting started',
link: '/guide/react-native/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/react-native/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/react-native/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/react-native/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/react-native/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Global styling',
link: '/guide/react-native/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/react-native/advanced/with-lucide-lab',
desc: 'Using lucide-lab with lucide-react-native',
},
{
text: 'Filled icons',
link: '/guide/react-native/advanced/filled-icons',
desc: 'Using filled icons in lucide-react-native',
},
{
text: 'Aliased Names',
link: '/guide/react-native/advanced/aliased-names',
desc: 'Using aliased icon names',
},
{
text: 'Combining icons',
link: '/guide/react-native/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
],
},
{
text: 'Resources',
items: [
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,103 @@
import { DefaultTheme } from "vitepress";
export const reactSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/react/',
},
{
text: 'Getting started',
link: '/guide/react/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/react/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/react/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/react/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/react/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/react/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/react/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/react/advanced/with-lucide-lab',
desc: 'Using lucide-lab with lucide-react',
},
// {
// text: 'Animations',
// link: '/guide/react/advanced/animations',
// desc: 'Add animations to your icons',
// },
{
text: 'Filled icons',
link: '/guide/react/advanced/filled-icons',
desc: 'Using filled icons in lucide-react',
},
{
text: 'Aliased Names',
link: '/guide/react/advanced/aliased-names',
desc: 'Using aliased icon names',
},
{
text: 'Combining icons',
link: '/guide/react/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
{
text: 'Dynamic icon component',
link: '/guide/react/advanced/dynamic-icon-component.md',
desc: 'Dynamically import icons as needed',
}
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,98 @@
import { DefaultTheme } from "vitepress";
export const solidSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/solid/',
},
{
text: 'Getting started',
link: '/guide/solid/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/solid/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/solid/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/solid/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/solid/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/solid/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/solid/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/solid/advanced/with-lucide-lab',
desc: 'Using lucide-lab with lucide-solid',
},
// {
// text: 'Animations',
// link: '/guide/solid/advanced/animations',
// desc: 'Add animations to your icons',
// },
{
text: 'Filled icons',
link: '/guide/solid/advanced/filled-icons',
desc: 'Using filled icons in lucide-solid',
},
{
text: 'Aliased Names',
link: '/guide/solid/advanced/aliased-names',
desc: 'Using aliased icon names',
},
{
text: 'Combining icons',
link: '/guide/solid/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,87 @@
import { DefaultTheme } from "vitepress";
export const svelteSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/svelte/',
},
{
text: 'Getting started',
link: '/guide/svelte/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/svelte/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/svelte/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/svelte/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/svelte/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/svelte/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/svelte/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/svelte/advanced/with-lucide-lab',
desc: 'Using lucide-lab with @lucide/svelte',
},
{
text: 'Filled icons',
link: '/guide/svelte/advanced/filled-icons',
desc: 'Using filled icons in @lucide/svelte',
},
{
text: 'Combining icons',
link: '/guide/svelte/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,92 @@
import { DefaultTheme } from "vitepress";
export const vueSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/vue/',
},
{
text: 'Getting started',
link: '/guide/vue/getting-started',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/vue/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/vue/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/vue/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/vue/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/vue/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/vue/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With lucide lab',
link: '/guide/vue/advanced/with-lucide-lab',
desc: 'Using lucide-lab with @lucide/vue',
},
{
text: 'Filled icons',
link: '/guide/vue/advanced/filled-icons',
desc: 'Using filled icons in @lucide/vue',
},
{
text: 'Aliased Names',
link: '/guide/vue/advanced/aliased-names',
desc: 'Using aliased icon names',
},
{
text: 'Combining icons',
link: '/guide/vue/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
text: 'VSCode',
link: '/guide/vscode',
desc: 'VSCode and Lucide',
},
],
}
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -5,8 +5,8 @@ export type IconNode = [elementName: string, attrs: Record<string, string>][]
const props = defineProps<{
name: string;
tags: string[];
categories: string[];
tags?: string[];
categories?: string[];
// contributors: Contributor[];
iconNode: IconNode;
}>()

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
defineProps<{
href?: string
title?: string
desc?: string
}>()
</script>
<template>
<VPLink
class="overview-link"
:href="href"
:aria-label="`${title} - ${desc}`"
>
<span class="title">
{{ title }}
</span>
<span
class="desc">
{{ desc }}
</span>
</VPLink>
</template>
<style scoped>
.overview-link {
display: block;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 11px 16px 13px;
width: 100%;
height: 100%;
transition: border-color 0.25s;
text-decoration: none;
}
.overview-link:hover {
border-color: var(--vp-c-brand-1);
}
.desc {
display: block;
line-height: 20px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
}
.title {
display: block;
line-height: 20px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: color 0.25s;
margin-bottom: 2px;
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<div class="overview-link-grid">
<slot />
</div>
</template>
<style>
.overview-link-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
.overview-link-grid > * {
box-sizing: border-box;
padding: 8px;
}
@media (min-width: 960px) {
.overview-link-grid {
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
}
@media (min-width: 1280px) {
.overview-link-grid {
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
}
</style>

View File

@@ -0,0 +1,226 @@
<script setup lang="ts">
import { ref } from 'vue'
import {
Listbox,
ListboxLabel,
ListboxButton,
ListboxOptions,
ListboxOption,
} from '@headlessui/vue'
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-vue-next'
defineProps<{
id?: string
items?: { name: string, icon: string }[]
}>()
const selected = defineModel<{ name: string, icon: string }>()
</script>
<template>
<Listbox v-model="selected">
<div class="select-wrapper">
<ListboxButton class="select-button" :id="id">
<img
:src="selected.icon"
:class="{ 'select-item-icon': true }"
:alt="`${selected.name} logo`"
loading="lazy"
/>
<span class="select-text">{{ selected.name }}</span>
<span class="select-icon">
<ChevronsUpDownIcon class="chevron-icon" aria-hidden="true" />
</span>
</ListboxButton>
<transition
leave-active-class="transition-leave"
leave-from-class="transition-leave-from"
leave-to-class="transition-leave-to"
>
<ListboxOptions class="select-options">
<ListboxOption
v-slot="{ active, selected }"
v-for="item in items"
:key="item.name"
:value="item"
as="template"
>
<li :class="['select-option', { active, selected }]">
<img
:src="item.icon"
:class="{ 'select-item-icon': true }"
:alt="`${item.name} logo`"
loading="lazy"
/>
<span :class="['option-text', { selected }]">{{ item.name }}</span>
<span v-if="selected" class="check-icon">
<CheckIcon class="check" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
<style scoped>
.select-wrapper {
position: relative;
}
.select-button {
background: var(--vp-sidebar-input);
border-radius: 8px;
color: var(--vp-c-text-1);
padding: 7px 14px;
height: auto;
font-size: 14px;
border: 1px solid transparent;
cursor: text;
display: flex;
gap: 12px;
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
width: 100%;
align-items: center;
}
.select-button:focus {
border-color: var(--vp-c-brand);
}
.select-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.select-icon {
pointer-events: none;
position: absolute;
top: 0;
bottom: 0;
right: 0;
display: flex;
align-items: center;
padding-right: 0.5rem;
}
.select-item-icon {
object-fit: contain;
width: 24px;
height: 24px;
}
.chevron-icon {
height: 1.25rem;
width: 1.25rem;
color: #9ca3af;
}
.transition-leave {
transition: opacity 100ms ease-in;
}
.transition-leave-from {
opacity: 1;
}
.transition-leave-to {
opacity: 0;
}
.select-options {
position: absolute;
display: flex;
flex-direction: column;
border-radius: 12px;
padding: 12px;
min-width: 128px;
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg-elv);
box-shadow: var(--vp-shadow-3);
transition: background-color 0.5s;
max-height: calc(100vh - var(--vp-nav-height));
overflow-y: auto;
width: 100%;
z-index: 90;
right: 0;
top: 44px;
}
.select-option {
position: relative;
cursor: default;
user-select: none;
padding: 0px 4px;
text-align: left;
display: flex;
align-items: center;
gap: 12px;
border-radius: 6px;
line-height: 32px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-1);
white-space: nowrap;
transition: background-color .25s,color .25s;
list-style: none;
}
.select-option:hover, .select-option.active {
color: var(--vp-c-brand);
background-color: var(--vp-c-default-soft);
}
.select-option:active {
color: var(--vp-c-brand);
background-color: var(--vp-c-bg-elv);
}
.option-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: normal;
}
.option-text.selected {
font-weight: 500;
}
.check-icon {
position: absolute;
top: 0;
bottom: 0;
right: 12px;
display: flex;
align-items: center;
padding-left: 0.75rem;
color: var(--vp-c-brand);
}
.check {
height: 1.25rem;
width: 1.25rem;
}
@media (min-width: 640px) {
.select-button,
.select-options {
font-size: 0.875rem;
line-height: 1.25rem;
}
}
</style>

View File

@@ -0,0 +1,136 @@
<script setup lang="ts">
import { getSandpackFiles, getCustomSetupFromProps, parsedBoolean, getSandpackOptions } from 'vitepress-plugin-sandpack';
import { Sandpack, type SandpackFiles } from 'sandpack-vue3';
import { computed, nextTick, onBeforeMount, onMounted, ref, useSlots, watch } from 'vue';
import styles from './styles.css?raw'
import sandpackTheme from '../../sandpackTheme.json'
import { sandboxProps } from 'vitepress-plugin-sandpack';
const props = defineProps({
...sandboxProps,
editorHeight: {
type: [String, Number],
default: undefined
},
editorWidthPercentage: {
type: [String, Number],
default: undefined
},
dependencies: {
type: String,
default: undefined
}
});
const files = ref<SandpackFiles>({});
const getOpt = (propName: string) => props[propName] ?? props?.options?.[propName];
const editorVisible = computed(() => parsedBoolean(getOpt('hideEditor')) ? 'none' : 'flex');
const previewHeight = computed(() => isNaN(Number(getOpt('previewHeight'))) ? undefined : Number(getOpt('previewHeight')));
const previewHeightStyle =
computed(() => previewHeight.value ? `${previewHeight.value}px` : 'var(--sp-layout-height)');
const coderHeight = computed(() => isNaN(Number(getOpt('coderHeight'))) ? undefined : Number(getOpt('coderHeight')));
const coderHeightStyle =
computed(() => coderHeight.value ? `${coderHeight.value}px` : 'var(--sp-layout-height)');
const slots = useSlots();
const isDark = ref(false);
const resolveFiles = async () => {
files.value = {
...await getSandpackFiles(props, slots),
'styles.css': {
code: styles,
hidden: true
},
};
};
watch(props, resolveFiles);
onBeforeMount(resolveFiles);
const dependencies = computed(() => {
if (props.dependencies) {
return props.dependencies.split(',').reduce((acc, dep) => {
const [name, version] = dep.split(':').map(s => s.trim());
acc[name] = version || 'latest';
return acc;
}, {} as Record<string, string>);
}
return undefined;
})
</script>
<template>
<Sandpack :theme="sandpackTheme" :template="template" :rtl="parsedBoolean(rtl)" :files="files" :options="{
...(getSandpackOptions(props) as any),
editorWidthPercentage: getOpt('editorWidthPercentage') ? Number(getOpt('editorWidthPercentage')) : undefined,
showConsoleButton: false,
}" :customSetup='{
dependencies: dependencies
}' />
</template>
<style>
.sp-wrapper+* {
margin-top: 24px;
}
.sp-wrapper .sp-layout {
border-radius: 8px;
}
.sp-wrapper .sp-tabs-scrollable-container {
border-radius: 8px 8px 0 0;
position: relative;
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
margin-bottom: 0px;
margin-top: -1px;
height: 48px;
padding-bottom: 1px;
}
.sp-wrapper .sp-preview-container {
background-color: transparent;
}
.sp-wrapper .sp-tabs .sp-tab-button {
padding: 0 12px;
line-height: 48px;
height: 48px;
font-size: 14px;
font-weight: 500;
position: relative;
}
.sp-wrapper .sp-tabs .sp-tab-button:after {
position: absolute;
right: 8px;
left: 8px;
bottom: 0px;
z-index: 1;
height: 1px;
content: '';
background-color: transparent;
transition: background-color 0.25s;
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
color: var(--vp-code-tab-active-text-color);
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
background-color: var(--vp-code-tab-active-bar-color);
}
.sp-wrapper .sp-button {
color: var(--sp-colors-clickable);
text-decoration: none;
}
</style>

View File

@@ -0,0 +1,48 @@
<script setup lang="ts">
import { useMutationObserver, useScriptTag } from '@vueuse/core';
import { useData } from 'vitepress';
import { onMounted, useTemplateRef, watchEffect } from 'vue';
const { isDark } = useData()
const el = useTemplateRef('el')
useScriptTag('https://snack.expo.dev/embed.js')
watchEffect(() => {
console.log(isDark.value);
})
useMutationObserver(el, (mutations) => {
const container = el.value;
if (mutations[0]) {
if ('ExpoSnack' in window) {
window?.ExpoSnack?.remove(container);
window?.ExpoSnack?.append(container);
}
}
}, {
attributes: true,
})
onMounted(() => {
const container = el.value;
if ('ExpoSnack' in window) {
window?.ExpoSnack?.append(container);
}
})
</script>
<template>
<div v-bind="$attrs" class="snack-player" ref="el" :data-snack-theme="isDark ? 'dark' : 'light'" />
</template>
<style>
.snack-player {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
width: 100%;
height: 635px;
margin-bottom: 24px;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,50 @@
body {
font-family: sans-serif;
-webkit-font-smoothing: auto;
-moz-font-smoothing: auto;
-moz-osx-font-smoothing: grayscale;
font-smoothing: auto;
text-rendering: optimizeLegibility;
font-smooth: always;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
background: #202127;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
color: #fff;
}
html,
body {
height: 100%;
min-height: 100%;
}
button {
display: flex;
align-items: center;
font-size: 18px;
padding: 10px 20px;
line-height: 24px;
gap: 8px;
border-radius: 24px;
outline: none;
border: none;
background: #111;
transition: all 0.3s ease;
}
button:hover {
background: #f56565;
}
.app {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
}

View File

@@ -0,0 +1,88 @@
<script setup lang="tsx">
import VPSidebarGroup from 'vitepress/dist/client/theme-default/components/VPSidebarGroup.vue';
import sidebar, { guideSidebarTop } from '../../../sidebar';
import { useData, useRouter } from 'vitepress';
import Select from '../base/Select.vue';
import { computed, ref, watch, watchEffect } from 'vue';
import { link, route } from '~/.vitepress/data/iconNodes';
import { useLocalStorage } from '@vueuse/core';
const { page } = useData()
const router = useRouter()
const frameworks = [
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide' },
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react' },
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue' },
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte' },
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid' },
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular' },
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact' },
{ name: 'React Native', icon: '/framework-logos/react-native.svg', route: '/guide/react-native' },
{ name: 'Astro', icon: '/framework-logos/astro-dark.svg', route: '/guide/astro' },
]
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1])
const selected = computed(() => {
const current = frameworks.find(({ route }) => {
return router.route.path.split('/').slice(0, 3).join('/') === route
})
return current || fallbackFramework.value
})
function onSelectFramework(item: { name: string, icon: string, route: string }) {
fallbackFramework.value = item
if (item.route !== router.route.path) {
const likeRoute = router.route.path.replace(selected.value.route, item.route);
const hasRoute = sidebar[item.route]?.some(section =>
section?.items?.some(({ link }) => link === likeRoute)
);
if (hasRoute) {
router.go(likeRoute)
return;
}
router.go(item.route)
}
}
</script>
<template>
<VPSidebarGroup :items="guideSidebarTop" v-if="page?.relativePath?.startsWith?.('guide')" />
<div class="framework-select" v-if="page?.relativePath?.startsWith?.('guide')">
<label for="framework-select">Framework</label>
<Select id="framework-select" :items="frameworks" @update:model-value="onSelectFramework" v-model="selected" />
</div>
<VPSidebarGroup :key="selected.route"
v-if="page?.relativePath?.startsWith?.('guide') && !page?.relativePath?.startsWith?.(selected.route.substring(1))"
:items="sidebar[selected.route]" />
</template>
<style scoped>
.framework-select {
font-size: 12px;
transition: border-color 0.5s, background-color 0.5s ease;
margin-bottom: 10px;
position: sticky;
top: -0.5px;
z-index: 10;
border-top: 1px solid var(--vp-c-divider);
padding-top: 10px;
margin-top: -10px;
}
label {
color: var(--vp-c-text-1);
padding: 4px 0;
line-height: 24px;
font-size: 14px;
transition: color 0.25s;
font-weight: bold;
margin-bottom: 4px;
display: block;
}
</style>

View File

@@ -0,0 +1,51 @@
<script setup lang="ts">
import { useRouter } from 'vitepress';
import Badge from '../base/Badge.vue';
import HomeContainer from './HomeContainer.vue';
import { data } from './HomeHeroIconsCard.data'
import FakeInput from '../base/FakeInput.vue'
import { nextTick, provide } from 'vue'
import useSearchShortcut from '../../utils/useSearchShortcut';
const { go } = useRouter()
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
go('/icons/?focus')
})
const enableTransitions = () =>
'startViewTransition' in document &&
window.matchMedia('(prefers-reduced-motion: no-preference)').matches
async function handleClick(event: MouseEvent) {
if (!enableTransitions()) {
go('/icons/?focus')
return;
}
await document.startViewTransition(async () => {
await go('/icons/?focus');
await nextTick()
}).ready
}
</script>
<template>
<FakeInput
@click="go('/icons/?focus')"
:shortcut="kbdSearchShortcut"
class="search-box"
>
Search {{ data.iconsCount }} icons...
</FakeInput>
</template>
<style scoped>
.search-box {
view-transition-name: icons-search-box;
width: 100%;
margin-top: 24px;
}
</style>

View File

@@ -0,0 +1,428 @@
<script setup lang="ts">
import { ref, onMounted, shallowRef, onBeforeUnmount, watchEffect, computed } from 'vue';
import { data } from './HomeHeroIconsCard.data'
import { useRouter } from 'vitepress';
import { random } from 'lodash-es'
import FakeInput from '../base/FakeInput.vue'
import { motion, Variants, useScroll, useSpring, useTransform } from "motion-v"
import LucideIcon from '../base/LucideIcon.vue'
import { shrink } from '~/.vitepress/data/iconNodes';
const emit = defineEmits(['animation-complete'])
const MotionLucideIcon = motion.create(LucideIcon)
const COLUMNS = 8;
const SIZE = 2;
const GAP = 1;
const { scrollYProgress } = useScroll()
const opacity = useTransform(() => (1 - scrollYProgress.get() * 8))
const icons = ref(data.icons.slice(0, 64).map((icon, index) => {
const x = index % COLUMNS;
const y = Math.floor(index / COLUMNS);
if (index === 0) {
return {
...icon,
x: 9999,
y: 9999,
opacity: 0
}
}
return {
...icon,
x: x * (SIZE + GAP) + 0.5,
y: y * (SIZE + GAP) + 0.5
}
}))
const { go } = useRouter()
const intervalTime = shallowRef()
const showHandles = ref(true)
const scaleDownVariants: Variants = {
fullSize: {
scale: 1
},
riseUp: {
x: 0.5,
y: -0.5,
animationName: 'riseUp',
scale: 1,
transition: {
delay: 0.5,
duration: 1.5,
ease: [0.22, 1, 0.36, 1]
}
},
small: {
x: -10.5,
y: -10.5,
scale: 0.1,
animationName: 'small',
transition: {
delay: 1,
duration: 1,
ease: [0.22, 1, 0.36, 1]
}
}
}
const scaleDownAnimation = ref('fullSize')
const iconGridAnimation = ref('initial')
const drawAnimation = ref('visible')
const draw: Variants = {
hidden: { pathLength: 0, opacity: 0 },
visible: {
animationName: 'visible',
pathLength: 1,
opacity: 1,
transition: {
pathLength: { delay: 2.4, type: "spring", duration: 2.8, bounce: 0 },
opacity: { delay: 2.4, duration: 0.1 },
},
},
exit: (path) => ({
animationName: 'exit',
stroke: path ? 'var(--vp-c-text-1)' : 'var(--vp-c-brand)',
pathLength: 1,
opacity: 1,
transition: {
duration: 0.8,
},
}),
};
const onAnimationComplete = (item) => {
if (item.animationName === 'visible') {
drawAnimation.value = 'exit'
return
}
if (item.animationName === 'exit') {
showHandles.value = false
scaleDownAnimation.value = 'small'
}
if (item.animationName === 'small') {
iconGridAnimation.value = 'showIcons'
}
if (item.animationName === 'riseUp') {
scaleDownAnimation.value = 'small'
}
if (item.animationName === 'showIcons') {
shrinkIconAnimation.value = 'shrinkIcons'
}
if (item.animationName === 'shrinkIcons') {
iconGridAnimation.value = 'initial'
setTimeout(() => {
emit('animation-complete')
}, 2800)
}
}
const randomIndex = ref(
Math.floor(Math.random() * 64)
)
const iconAnimationVariants = {
initial: {
animationName: 'end',
opacity: 0,
x: 0,
y: 0,
transition: { duration: 1, delay: 1, ease: 'easeInOut' }
},
showIcons: (index) => ({
animationName: 'showIcons',
opacity: [0, 1, 1],
x: [0.5, 0, 0],
y: [-0.5, 0, 0],
strokeWidth: randomIndex.value === index ? [0, 2, 2] : undefined,
transition: { delay: index * 0.023, duration: 1.6, ease: 'easeInOut' }
}),
}
const shrinkIconAnimation = ref('initial')
const shrinkIconVariants = {
initial: { strokeWidth: 2 },
shrinkIcons: (index) => ({
animationName: 'shrinkIcons',
opacity: 1,
strokeWidth: 0,
transition: { delay: 1.8, duration: 1.5, ease: 'easeInOut' }
})
}
</script>
<template>
<div class="home-hero-animation-container">
<div class="home-hero-animation">
<motion.svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="hero-background"
:style="{ opacity }">
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
mask="url(#svg-preview-bounding-box-mask)" stroke-opacity="0.3">
<path
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
stroke-width="0.1"
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
</path>
<path
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
</path>
</g>
<!-- <rect fill="red" x="0" y="0" width="24" height="24" fill-opacity="0.1" stroke="none" /> -->
<motion.g initial="initial" :variants="shrinkIconVariants" :animate="shrinkIconAnimation"
@animation-complete="onAnimationComplete">
<MotionLucideIcon v-for="(icon, index) in icons" size="2" class="animated-icon" initial="initial"
:key="icon.name" :variants="iconAnimationVariants" :animate="iconGridAnimation" :custom="index"
strokeWidth="inherit" v-bind="icon" @animation-complete="onAnimationComplete" />
<motion.g class="svg-preview-colored-path-group" :variants="scaleDownVariants" :animate="scaleDownAnimation"
initial="hidden" @animation-complete="onAnimationComplete">
<motion.path
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
:custom="1" @animation-complete="onAnimationComplete" />
<motion.path
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
:style="{ stroke: 'var(--vp-c-gray-1)' }" :animate="drawAnimation" initial="hidden" :variants="draw"
:custom="0" />
</motion.g>
</motion.g>
<motion.g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125"
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
:transition="{ delay: 0, duration: 0.2 }">
<motion.path
d="M14 12C14 9.79086 12.2091 8 10 8C7.79086 8 6 9.79086 6 12C6 16.4183 9.58172 20 14 20C18.4183 20 22 16.4183 22 12C22 8.446 20.455 5.25285 18 3.05557"
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
<motion.path
d="M10 12C10 14.2091 11.7909 16 14 16C16.2091 16 18 14.2091 18 12C18 7.58172 14.4183 4 10 4C5.58172 4 2 7.58172 2 12C2 15.5841 3.57127 18.8012 6.06253 21"
:initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 1.6, duration: 1.5 }" />
<motion.g :initial="{ opacity: 0 }" :animate="{ opacity: 1 }" :transition="{ delay: 0.2, duration: 0.3 }">
<path
d="M14 12h.01M10 8h.01M10 8h.01M6 12h.01M6 12h.01M14 20h.01M14 20h.01M22 12h.01M22 12h.01M18 3.05557h.01M10 12h.01M14 16h.01M14 16h.01M18 12h.01M18 12h.01M10 4h.01M10 4h.01M2 12h.01M2 12h.01M6.06253 21h.01">
</path>
</motion.g>
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }" cx="14" cy="12" r="0.5" />
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }" cx="18" cy="3.05557" r="0.5" />
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }" cx="10" cy="12" r="0.5" />
<motion.circle :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0, duration: 0.8 }" cx="6.06253" cy="21" r="0.5" />
</motion.g>
<motion.g class="svg-preview-handles-group" stroke-width="0.12" stroke="#FFF" stroke-opacity="0.3"
:initial="{ opacity: 1 }" :animate="showHandles ? { opacity: 1 } : { opacity: 0 }"
:transition="{ delay: 0, duration: 0.6 }">
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }">
<path d="M14 12 14 9.79086"></path>
<circle cy="9.79086" cx="14" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.4, duration: 0.3 }">
<path d="M10 8 12.2091 8"></path>
<circle cy="8" cx="12.2091" r="0.25"></circle>
<path d="M10 8 7.79086 8"></path>
<circle cy="8" cx="7.79086" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.6, duration: 0.3 }">
<path d="M6 12 6 9.79086"></path>
<circle cy="9.79086" cx="6" r="0.25"></circle>
<path d="M6 12 6 16.4183"></path>
<circle cy="16.4183" cx="6" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.8, duration: 0.3 }">
<path d="M14 20 9.58172 20"></path>
<circle cy="20" cx="9.58172" r="0.25"></circle>
<path d="M14 20 18.4183 20"></path>
<circle cy="20" cx="18.4183" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1, duration: 0.3 }">
<path d="M22 12 22 16.4183"></path>
<circle cy="16.4183" cx="22" r="0.25"></circle>
<path d="M22 12 22 8.446"></path>
<circle cy="8.446" cx="22" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1.2, duration: 0.3 }">
<path d="M18 3.05557 20.455 5.25285"></path>
<circle cy="5.25285" cx="20.455" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }">
<path d="M10 12 10 14.2091"></path>
<circle cy="14.2091" cx="10" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.4, duration: 0.3 }">
<path d="M14 16 11.7909 16"></path>
<circle cy="16" cx="11.7909" r="0.25"></circle>
<path d="M14 16 16.2091 16"></path>
<circle cy="16" cx="16.2091" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.6, duration: 0.3 }">
<path d="M18 12 18 14.2091"></path>
<circle cy="14.2091" cx="18" r="0.25"></circle>
<path d="M18 12 18 7.58172"></path>
<circle cy="7.58172" cx="18" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.8, duration: 0.3 }">
<path d="M10 4 14.4183 4"></path>
<circle cy="4" cx="14.4183" r="0.25"></circle>
<path d="M10 4 5.58172 4"></path>
<circle cy="4" cx="5.58172" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1, duration: 0.3 }">
<path d="M2 12 2 7.58172"></path>
<circle cy="7.58172" cx="2" r="0.25"></circle>
<path d="M2 12 2 15.5841"></path>
<circle cy="15.5841" cx="2" r="0.25"></circle>
</motion.g>
<motion.g :initial="{ opacity: 0, scale: 0.2 }" :animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 1.2, duration: 0.3 }">
<path d="M6.06253 21 3.57127 18.8012"></path>
<circle cy="18.8012" cx="3.57127" r="0.25"></circle>
</motion.g>
</motion.g>
</motion.svg>
</div>
</div>
</template>
<style scoped>
.home-hero-animation-container {
margin: -48px -24px 0;
display: flex;
}
.home-hero-animation {
height: 250px;
width: 396px;
overflow: hidden;
margin: auto;
margin-left: calc(((396px - 100vw) / 2)* -1);
}
@media (min-width: 396px) {
.home-hero-animation {
margin-left: auto;
}
}
.hero-background {
transform: rotateX(-51deg) rotateZ(-43deg);
transform-style: preserve-3d;
will-change: transform, opacity;
position: relative;
top: -155px;
left: -82px;
width: 560px;
height: 560px;
}
@media (min-width: 640px) {
.hero-background {
width: 680px;
height: 680px;
left: -100px;
top: -188px;
}
.home-hero-animation {
height: 305px;
width: 480px;
}
}
@media (min-width: 768px) {
.hero-background {
width: 760px;
height: 760px;
left: -110px;
top: -200px;
}
.home-hero-animation {
height: 360px;
width: 540px;
}
.home-hero-animation-container {
margin-top: -60px;
}
}
@media (min-width: 960px) {
.hero-background {
top: -20vw;
right: 20vw;
width: 80vw;
height: 80vw;
}
.home-hero-animation {
height: 415px;
width: 620px;
}
.home-hero-animation-container {
margin: -48px -48px 0 -64px;
}
}
@media (min-width: 1160px) {
.home-hero-animation-container {
margin-right: -64px;
margin-bottom: -180px;
}
.home-hero-animation {
width: auto;
height: calc(((1152px/2)));
top: -20px;
}
.hero-background {
top: -20vw;
}
}
@media (min-width: 1280px) {
.home-hero-animation-container {
margin-right: calc(((((100vw - 1152px) / 2)) * -1) + 24px);
margin-left: -128px
}
.hero-background {
width: 1024px;
height: 1024px;
top: -280px;
}
}
</style>

View File

@@ -1,162 +1,13 @@
<script setup lang="ts">
import { ref, onMounted, shallowRef, onBeforeUnmount} from 'vue';
import { data } from './HomeHeroIconsCard.data'
import LucideIcon from '../base/LucideIcon.vue'
import { useRouter } from 'vitepress';
import { random } from 'lodash-es'
import FakeInput from '../base/FakeInput.vue'
import useSearchShortcut from '../../utils/useSearchShortcut'
const { go } = useRouter()
const intervalTime = shallowRef()
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
go('/icons/?focus')
})
const getInitialItems = () => data.icons.slice(0, 48)
const items = ref(getInitialItems())
let id = items.value.length + 1
function getRandomNewIcon() {
const randomIndex = random(0, 200)
const newRandomIcon = data.icons[randomIndex]
if (items.value.some((item) => item.name === newRandomIcon.name)) {
return getRandomNewIcon()
}
return newRandomIcon
}
function insert() {
const replaceIndex = random(0, 48)
const newIcon = getRandomNewIcon()
items.value[replaceIndex] = newIcon
}
function startInterval() {
intervalTime.value = setInterval(() => {
insert()
}, 2000)
}
// TODO: Try maybe something else for better pref performance
onMounted(() => {
window.addEventListener('mousemove', startInterval, { once: true })
})
onBeforeUnmount(() => {
clearInterval(intervalTime.value)
})
import { ref } from 'vue';
import HomeHeroIconsAnimation from './HomeHeroIconsAnimation.vue'
const animationRun = ref(1)
</script>
<template>
<div class="card-wrapper">
<div class="icons-card">
<div class="card-grid">
<TransitionGroup name="list" mode="out-in">
<div
v-for="icon in items"
:key="icon.name"
class="random-icon"
>
<LucideIcon
v-bind="icon"
/>
</div>
</TransitionGroup>
</div>
<FakeInput
@click="go('/icons/?focus')"
:shortcut="kbdSearchShortcut"
class="search-box"
>
Search {{ data.iconsCount }} icons...
</FakeInput>
</div>
</div>
<HomeHeroIconsAnimation
:key="animationRun"
@animation-complete="animationRun++"
/>
</template>
<style scoped>
.card-wrapper {
margin-left: auto;
margin-bottom: auto;
margin-top: 48px;
}
.icons-card {
background: var(--vp-c-bg-alt);
padding: 24px;
border-radius: 8px;
width: 100%;
height:100%;
max-height: 220px;
max-width: 560px;
margin: 0 auto;
position: relative;
}
.card-grid {
display: grid;
gap: 8px;
grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(36px, 1fr));
width: 100%;
height:100%;
max-height: 168px;
max-width: 512px;
overflow: hidden;
position: relative;
}
.list-enter-active {
transition: all 0.5s cubic-bezier(.85,.85,.25,1.1);
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: scale(0.01);
}
.list-leave-active {
position: absolute;
opacity: 0;
display: none;
}
.search-box {
position: absolute;
width: 100%;
left: 0;
top: -64px;
}
.random-icon {
display: inline-flex;
justify-content: center;
align-items: center;
}
@media (min-width: 960px) {
.search-box {
top: unset;
bottom: -24px;
left: -24px;
box-shadow: var(--vp-shadow-3);
background: var(--vp-c-bg);
}
.dark .search-box {
background: var(--vp-c-bg-soft);
}
.card-wrapper {
margin-top: 8px;
}
}
</style>

View File

@@ -1,25 +1,22 @@
<script setup lang="ts">
import Badge from '../base/Badge.vue';
import HomeContainer from './HomeContainer.vue';
import { data } from './HomeHeroBefore.data'
import { data } from './HomeHeroInfoBefore.data'
</script>
<template>
<HomeContainer class="container">
<Badge
:href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`"
>v{{ data.version }}</Badge>
</HomeContainer>
<Badge :href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`">v{{ data.version }}</Badge>
</template>
<style scoped>
.container {
margin-block: 0;;
margin-block: 0;
margin-top: 37px;
margin-bottom: -96px;
display: flex;
justify-content: center;
}
.badge {
display: inline-block;
}
@@ -34,9 +31,9 @@ import { data } from './HomeHeroBefore.data'
.container {
justify-content: flex-start;
}
.badge {
display: inline-block;
}
}
</style>

View File

@@ -13,9 +13,9 @@ export default {
label: 'Lucide documentation for React',
},
{
name: 'lucide-vue-next',
name: 'lucide-vue',
logo: '/framework-logos/vue.svg',
label: 'Lucide documentation for Vue 3',
label: 'Lucide documentation for Vue',
},
{
name: 'lucide-svelte',

View File

@@ -29,7 +29,12 @@ const props = defineProps<{
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
try {
return createLucideIcon(props.name, props.iconNode);
} catch (error) {
console.warn(`Icon ${props.name} not found, using fallback`);
return null;
}
});
const CalendarIcon = createLucideIcon('calendar', Calendar.iconNode);
@@ -61,7 +66,7 @@ const prettyName = props.name
</script>
<template>
<section class="showcase">
<section class="showcase" v-if="iconComponent">
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">

View File

@@ -19,11 +19,11 @@ const ICON_GRID_GAP = 8;
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
const itemsPerRow = columnSize.value || 10;
const visibleRows = Math.ceil(window.innerHeight / (ICON_SIZE + ICON_GRID_GAP));
return Math.min(itemsPerRow * (visibleRows + 2), 200);
return Math.min(itemsPerRow * (visibleRows + 2), 200);
});
const props = defineProps<{
@@ -182,6 +182,7 @@ function handleCloseDrawer() {
.input-wrapper {
width: 100%;
view-transition-name: icons-search-box;
}
.overview-container {

View File

@@ -0,0 +1,379 @@
<script setup lang="ts">
import VPHero from 'vitepress/dist/client/theme-default/components/VPHero.vue'
import { useData } from 'vitepress/dist/client/theme-default/composables/data'
import FakeInput from '../base/FakeInput.vue';
import { useRouter } from 'vitepress';
import { data } from '../home/HomeHeroIconsCard.data'
import { useScroll } from '@vueuse/core';
import { computed } from 'vue';
const { go } = useRouter()
const { frontmatter: fm } = useData()
const { x, y, isScrolling, arrivedState, directions } = useScroll(window)
const opacity = computed(() => {
if (y.value < 0) return 1
if (y.value > 300) return 0
return 1 - (y.value / 300)
})
</script>
<template>
<VPHero v-if="fm.hero" class="VPHomeHero" :name="fm.hero.name" :text="fm.hero.text" :tagline="fm.hero.tagline"
:image="undefined" :actions="fm.hero.actions">
<template #home-hero-image></template>
<template #home-hero-actions-after>
<FakeInput @click="go('/icons/?focus')" class="search-box">
Search {{ data.iconsCount }} icons...
</FakeInput>
</template>
</VPHero>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-12 -12 48 48" fill="none" overflow="auto"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="hero-background" :style="{ opacity: opacity }">
<g class="svg-preview-grid-group" stroke-linecap="butt" stroke-width="0.1" stroke="#777"
stroke-opacity="0.3">
<!-- <rect class="svg-preview-grid-rect" width="23.9" height="23.9" x="0.05" y="0.05" rx="1"></rect> -->
<path
stroke-dasharray="0 0.1 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0.1 0.15 0 0.15"
stroke-width="0.1"
d="M1 0.1v23.8M2 0.1v23.8M4 0.1v23.8M5 0.1v23.8M7 0.1v23.8M8 0.1v23.8M10 0.1v23.8M11 0.1v23.8M13 0.1v23.8M14 0.1v23.8M16 0.1v23.8M17 0.1v23.8M19 0.1v23.8M20 0.1v23.8M22 0.1v23.8M23 0.1v23.8M0.1 1h23.8M0.1 2h23.8M0.1 4h23.8M0.1 5h23.8M0.1 7h23.8M0.1 8h23.8M0.1 10h23.8M0.1 11h23.8M0.1 13h23.8M0.1 14h23.8M0.1 16h23.8M0.1 17h23.8M0.1 19h23.8M0.1 20h23.8M0.1 22h23.8M0.1 23h23.8">
</path>
<path
d="M3 0.1v23.8M6 0.1v23.8M9 0.1v23.8M12 0.1v23.8M15 0.1v23.8M18 0.1v23.8M21 0.1v23.8M0.1 3h23.8M0.1 6h23.8M0.1 9h23.8M0.1 12h23.8M0.1 15h23.8M0.1 18h23.8M0.1 21h23.8">
</path>
</g>
<g class="svg-preview-shadow-mask-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
<mask id="svg-preview-shadow-mask-0" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M4.9 16.1h.01M4.9 1.9h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-1" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M7.8 4.7h.01M7 12.2h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-2" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M12 7h.01M14 9h.01M12 11h.01M10 9h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-3" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M16.2 4.8h.01M17 12.27h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-4" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M19.1 1.9h.01M19.1 16h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-5" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M9.5 18h.01M14.5 18h.01"></path>
</mask>
<mask id="svg-preview-shadow-mask-6" maskUnits="userSpaceOnUse" stroke-opacity="1" stroke-width="4" stroke="#000">
<rect x="0" y="0" width="100%" height="100%" fill="#fff" stroke="none" rx="1"></rect>
<path d="M8 22h.01M12 11h.01M16 22h.01"></path>
</mask>
</g>
<!-- <g class="svg-preview-shadow-group" stroke-width="4" stroke="#777" stroke-opacity="0.15">
<path mask="url(#svg-preview-shadow-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
<path mask="url(#svg-preview-shadow-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 7 A2 2 0 0 1 14 9"></path>
<path mask="url(#svg-preview-shadow-mask-2)" d="M 14 9 A2 2 0 0 1 12 11"></path>
<path mask="url(#svg-preview-shadow-mask-2)" d="M 12 11 A2 2 0 0 1 10 9"></path>
<path mask="url(#svg-preview-shadow-mask-2)" d="M 10 9 A2 2 0 0 1 12 7"></path>
<path mask="url(#svg-preview-shadow-mask-3)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
<path mask="url(#svg-preview-shadow-mask-4)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
<path mask="url(#svg-preview-shadow-mask-5)" d="M 9.5 18 L 14.5 18"></path>
<path mask="url(#svg-preview-shadow-mask-6)" d="M 8 22 L 12 11"></path>
<path mask="url(#svg-preview-shadow-mask-6)" d="M 12 11 L 16 22"></path>
<path
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M12 7h.01M14 9h.01M12 11h.01M10 9h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M16 22h.01">
</path>
</g> -->
<g>
<defs xmlns="http://www.w3.org/2000/svg">
<pattern id="backdrop-pattern-:R4:" width=".1" height=".1" patternUnits="userSpaceOnUse"
patternTransform="rotate(45 50 50)">
<line stroke="red" stroke-width="0.1" y2="1"></line>
<line stroke="red" stroke-width="0.1" y2="1"></line>
</pattern>
</defs>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
</mask>
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
</mask>
<path
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
</mask>
<path
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18"
stroke="url(#backdrop-pattern-:R4:)" stroke-width="4" stroke-opacity="0.75"
mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
</mask>
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
</mask>
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
<path stroke="white"
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
</path>
</mask>
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-0" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
</mask>
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-0)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-1" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
</mask>
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-1)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-2" maskUnits="userSpaceOnUse">
<path stroke="white"
d="M 12 7 A2 2 0 0 1 14 9 M 14 9 A2 2 0 0 1 12 11 M 12 11 A2 2 0 0 1 10 9 M 10 9 A2 2 0 0 1 12 7 M 8 22 L 12 11 M 12 11 L 16 22 M 9.5 18 L 14.5 18">
</path>
</mask>
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-2)"></path>
</g>
<g stroke-width="4">
<mask id="svg-preview-backdrop-mask-:R4:-3" maskUnits="userSpaceOnUse">
<path stroke="white" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
</mask>
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="url(#backdrop-pattern-:R4:)" stroke-width="4"
stroke-opacity="0.75" mask="url(#svg-preview-backdrop-mask-:R4:-3)"></path>
</g>
</g>
<g class="svg-preview-handles-group" stroke-width="0.12" stroke="#777" stroke-opacity="0.6">
<path d="M4.9 16.1 1 12.2"></path>
<circle cy="12.2" cx="1" r="0.25"></circle>
<path d="M4.9 1.9 1 5.8"></path>
<circle cy="5.8" cx="1" r="0.25"></circle>
<path d="M16.2 4.8 18.2 6.8"></path>
<circle cy="6.8" cx="18.2" r="0.25"></circle>
<path d="M17 12.27 18.46 9.91"></path>
<circle cy="9.91" cx="18.46" r="0.25"></circle>
</g>
<g class="svg-preview-colored-path-group">
<path d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9" stroke="##dfdfd6"></path>
<path d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2" stroke="##dfdfd6"></path>
<path d="M 12 7 A2 2 0 0 1 14 9" stroke="##dfdfd6"></path>
<path d="M 14 9 A2 2 0 0 1 12 11" stroke="##dfdfd6"></path>
<path d="M 12 11 A2 2 0 0 1 10 9" stroke="##dfdfd6"></path>
<path d="M 10 9 A2 2 0 0 1 12 7" stroke="##dfdfd6"></path>
<path d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27" stroke="##dfdfd6"></path>
<path d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16" stroke="##dfdfd6"></path>
<path d="M 9.5 18 L 14.5 18" stroke="##dfdfd6"></path>
<path d="M 8 22 L 12 11" stroke="##dfdfd6"></path>
<path d="M 12 11 L 16 22" stroke="##dfdfd6"></path>
</g>
<g class="svg-preview-radii-group" stroke-width="0.12" stroke-dasharray="0 0.25 0.25" stroke="#777"
stroke-opacity="0.3">
<circle cx="9.518750780437157" cy="16.261333416579962" r="0.25"></circle>
<circle cx="11.118750780437157" cy="1.261333416579964" r="0.25"></circle>
<path
d="M9.518750780437157 16.261333416579962L4.481249219562843 8.138666583420036L11.118750780437157 1.261333416579964">
</path>
<circle cx="4.481249219562843" cy="8.138666583420036" r="0.25"></circle>
<path d="M7 12.2L12.217985863765243 8.963918492134958L7.8 4.7"></path>
<circle cy="8.963918492134958" cx="12.217985863765243" r="0.25" stroke-dasharray="0" stroke="red"></circle>
<circle cy="8.963918492134958" cx="12.217985863765243" r="6.14" stroke="red"></circle>
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
<circle cy="9" cx="12" r="2"></circle>
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
<circle cy="9" cx="12" r="2"></circle>
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
<circle cy="9" cx="12" r="2"></circle>
<circle cy="9" cx="12" r="0.25" stroke-dasharray="0"></circle>
<circle cy="9" cx="12" r="2"></circle>
<circle cx="12.035530040810755" cy="23.05" r="0.25"></circle>
<circle cx="12.035530040810755" cy="-5.1499999999999995" r="0.25"></circle>
<path d="M12.035530040810755 23.05L26.164469959189248 8.95L12.035530040810755 -5.1499999999999995"></path>
<circle cx="26.164469959189248" cy="8.95" r="0.25"></circle>
<path d="M19.1 16L12.064440320770494 8.95L19.1 1.9"></path>
<circle cy="8.95" cx="12.064440320770494" r="0.25" stroke-dasharray="0" stroke="red"></circle>
<circle cy="8.95" cx="12.064440320770494" r="9.96" stroke="red"></circle>
</g>
<g class="svg-preview-control-path-marker-mask-group" stroke-width="1" stroke="#000">
<mask id="svg-preview-control-path-marker-mask-0" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M4.9 16.1h.01"></path>
<path d="M4.9 1.9h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-1" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M7.8 4.7h.01"></path>
<path d="M7 12.2h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-6" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M16.2 4.8h.01"></path>
<path d="M17 12.27h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-7" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M19.1 1.9h.01"></path>
<path d="M19.1 16h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-8" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M9.5 18h.01"></path>
<path d="M14.5 18h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-9" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M8 22h.01"></path>
<path d="M12 11h.01"></path>
</mask>
<mask id="svg-preview-control-path-marker-mask-10" maskUnits="userSpaceOnUse">
<rect x="0" y="0" width="100%" height="100%" fill="#ccc" stroke="none" rx="1"></rect>
<path d="M12 11h.01"></path>
<path d="M16 22h.01"></path>
</mask>
</g>
<g class="svg-preview-control-path-group" stroke="#fff" stroke-width="0.125">
<path mask="url(#svg-preview-control-path-marker-mask-0)" d="M 4.9 16.1 C1 12.2 1 5.8 4.9 1.9"></path>
<path mask="url(#svg-preview-control-path-marker-mask-1)" d="M 7.8 4.7 A6.14 6.14 0 0 0 7 12.2"></path>
<path d="M 12 7 A2 2 0 0 1 14 9"></path>
<path d="M 14 9 A2 2 0 0 1 12 11"></path>
<path d="M 12 11 A2 2 0 0 1 10 9"></path>
<path d="M 10 9 A2 2 0 0 1 12 7"></path>
<path mask="url(#svg-preview-control-path-marker-mask-6)" d="M 16.2 4.8 C18.2 6.8 18.46 9.91 17 12.27"></path>
<path mask="url(#svg-preview-control-path-marker-mask-7)" d="M 19.1 1.9 A9.96 9.96 0 0 1 19.1 16"></path>
<path mask="url(#svg-preview-control-path-marker-mask-8)" d="M 9.5 18 L 14.5 18"></path>
<path mask="url(#svg-preview-control-path-marker-mask-9)" d="M 8 22 L 12 11"></path>
<path mask="url(#svg-preview-control-path-marker-mask-10)" d="M 12 11 L 16 22"></path>
</g>
<g class="svg-preview-control-path-marker-group" stroke="#fff" stroke-width="0.125">
<path
d="M4.9 16.1h.01M4.9 1.9h.01M7.8 4.7h.01M7 12.2h.01M16.2 4.8h.01M17 12.27h.01M19.1 1.9h.01M19.1 16h.01M9.5 18h.01M14.5 18h.01M8 22h.01M12 11h.01M12 11h.01M16 22h.01">
</path>
<circle cx="4.9" cy="16.1" r="0.5"></circle>
<circle cx="4.9" cy="1.9" r="0.5"></circle>
<circle cx="7.8" cy="4.7" r="0.5"></circle>
<circle cx="7" cy="12.2" r="0.5"></circle>
<circle cx="16.2" cy="4.8" r="0.5"></circle>
<circle cx="17" cy="12.27" r="0.5"></circle>
<circle cx="19.1" cy="1.9" r="0.5"></circle>
<circle cx="19.1" cy="16" r="0.5"></circle>
<circle cx="9.5" cy="18" r="0.5"></circle>
<circle cx="14.5" cy="18" r="0.5"></circle>
<circle cx="8" cy="22" r="0.5"></circle>
<circle cx="16" cy="22" r="0.5"></circle>
</g>
<g class="svg-preview-handles-group" stroke-width="0.12" style="stroke: var(--vp-c-brand)" stroke-opacity="0.3">
<path d="M4.9 16.1 1 12.2"></path>
<circle cy="12.2" cx="1" r="0.25"></circle>
<path d="M4.9 1.9 1 5.8"></path>
<circle cy="5.8" cx="1" r="0.25"></circle>
<path d="M16.2 4.8 18.2 6.8"></path>
<circle cy="6.8" cx="18.2" r="0.25"></circle>
<path d="M17 12.27 18.46 9.91"></path>
<circle cy="9.91" cx="18.46" r="0.25"></circle>
</g>
</svg>
</template>
<style>
.hero {
overflow: hidden;
height: 60vh;
display: flex;
position: relative;
/* align-items: center;
justify-content: center; */
}
.hero-background {
transform: rotateX(-51deg) rotateZ(-43deg);
transform-style: preserve-3d;
position: fixed;
top: -240px;
right: -320px;
width: 112vw;
height: 112vh;
}
.hero-title {
font-size: 3.2rem;
line-height: 1.2;
font-weight: 700;
text-align: center;
max-width: 800px;
}
/* .VPHomeHero .image {
display: none;
}
.VPHomeHero .container {
justify-content: center;
text-align: center;
}
.VPHomeHero .main {
text-align: center;
} */
/* .VPHomeHero .container .actions {
justify-content: center;
} */
/*
@media (min-width: 960px) {
.VPHomeHero :deep(.actions) {
justify-content: center;
}
} */
@media screen and (prefers-color-scheme: light) {
.svg-preview-grid-rect { fill: none }
}
@media screen and (prefers-color-scheme: dark) {
.svg-preview-grid-rect { fill: none }
.svg
.svg-preview-grid-group,
.svg-preview-radii-group,
.svg-preview-shadow-mask-group,
.svg-preview-shadow-group {
stroke: #fff;
}
}
.search-box {
/* width: calc(100vw - 272px); */
width: 100%;
margin-top: 24px;
}
</style>

View File

@@ -0,0 +1,248 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, type Component } from 'vue';
import {
heart,
star,
zap,
code,
feather,
cloud,
sun,
moon,
camera,
music,
video,
globe,
layers,
package as packageIcon,
compass,
command,
terminal,
database,
server,
cpu,
lock,
key,
shield,
wifi,
download,
upload,
search,
settings,
users,
mail,
bell,
calendar,
clock,
gitBranch,
funnel,
bookmark,
tag,
sparkles,
} from '../../../data/iconNodes';
import createElement from 'lucide/src/createElement';
import { IconNode } from 'lucide';
const icons: Component[] = [
heart, star, zap, code, feather, cloud, sun, moon, camera, music,
video, globe, layers, packageIcon, compass, command, terminal, database,
server, cpu, lock, key, shield, wifi, download, upload, search,
settings, users, mail, bell, calendar, clock, gitBranch, funnel,
bookmark, tag, sparkles,
];
const svgs = icons.map(icon => {
const element = createElement(icon as IconNode, {
stroke: 'white',
opacity: '0.2',
})
return element.outerHTML;
});
const highlightedSvgs = icons.map(icon => {
const element = createElement(icon as IconNode, {
stroke: 'white',
})
return element.outerHTML;
});
const images = svgs.map(svg => {
const img = new Image();
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
return img;
});
const highlightedImages = highlightedSvgs.map(svg => {
const img = new Image();
img.src = `data:image/svg+xml;base64,${btoa(svg)}`;
return img;
});
const canvas = ref<HTMLCanvasElement | null>(null);
// Setting up the columns
const fontSize = 16;
const gap = 40;
const gapY = 8;
const intervalTime = ref<number | null>(null);
// Store individual drops with their positions
let individualDrops: Array<{x: number, y: number, active: boolean}> = [];
// Random integer between min and max
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
onMounted(() => {
if (!canvas.value) return;
const ctx = canvas.value.getContext('2d');
if (!ctx) return;
const width = window.innerWidth;
const height = window.innerHeight * 0.6;
canvas.value.width = width;
canvas.value.height = height;
let columns = Math.floor(width / 120);
var rows = Math.floor(canvas.value.height / fontSize);
var midStart = Math.floor(rows * 0.25);
var midEnd = Math.floor(rows * 0.85);
let drops = Array.from({ length: columns }, () => randInt(midStart, midEnd));
let fps, fpsInterval, startTime, now, then, elapsed;
// Add click event listener
function handleCanvasClick(event: MouseEvent) {
const rect = canvas.value?.getBoundingClientRect();
if (!rect) return;
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
individualDrops.push({
x: x,
y: y,
active: true
});
}
canvas.value.addEventListener('click', handleCanvasClick);
function draw() {
if (!ctx) return;
ctx.fillStyle = 'rgba(27, 27, 31, 0.50)';
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
// Draw regular drops
for (var i = drops.length; i > 0; i--) {
var img = images[Math.floor(Math.random() * images.length)];
ctx.drawImage(img, i * fontSize * 2 + gap, drops[i] * fontSize, (fontSize / 2) + gapY, (fontSize / 2) + gapY);
drops[i]++;
if (Math.random() > .98) {
drops[i] = randInt(midStart, midEnd);
}
}
// Draw and update individual drops
individualDrops = individualDrops.filter(drop => {
if (!drop.active) return false;
var img = highlightedImages[Math.floor(Math.random() * images.length)];
ctx.drawImage(img, drop.x - fontSize/2, drop.y, fontSize, fontSize);
drop.y += fontSize
// Remove if off screen
if (drop.y > height) {
drop.active = false;
return false;
}
return true;
});
}
function startAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
animate();
}
function animate() {
// request another frame
requestAnimationFrame(animate);
// calc elapsed time since last loop
now = Date.now();
elapsed = now - then;
// if enough time has elapsed, draw the next frame
if (elapsed > fpsInterval) {
// Get ready for next frame by setting then=now, but also adjust for your
// specified fpsInterval not being a multiple of RAF's interval (16.7ms)
then = now - (elapsed % fpsInterval);
// Put your drawing code here
draw();
}
}
startAnimating(12);
window.addEventListener('resize', function() {
canvas.value.width = window.innerWidth;
canvas.value.height = window.innerHeight;
rows = Math.floor(canvas.value.height / fontSize);
columns = Math.floor(canvas.value.width / fontSize);
var oldDrops = drops.slice();
drops = [];
for (var i = 0; i < columns; i++) {
drops[i] = oldDrops[i % oldDrops.length] ?? randInt(midStart, midEnd);
}
});
// Cleanup event listener
onBeforeUnmount(() => {
canvas.value?.removeEventListener('click', handleCanvasClick);
});
});
</script>
<template>
<div class="hero">
<canvas ref="canvas" class="hero-canvas" />
</div>
</template>
<style scoped>
.hero {
overflow: hidden;
height: 60vh;
}
.hero-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 60vh;
filter: blur(0px);
}
</style>

View File

@@ -1,27 +1,36 @@
import { h } from 'vue';
import { h, nextTick, onMounted, watch } from 'vue';
import DefaultTheme from 'vitepress/theme';
import './style.css';
import { Theme } from 'vitepress';
import 'virtual:group-icons.css'
import { Theme, useRouter } from 'vitepress';
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
import HomeHeroAfter from './components/home/HomeHeroAfter.vue';
import HomeHeroInfoBefore from './components/home/HomeHeroInfoBefore.vue';
import { ICON_STYLE_CONTEXT, iconStyleContext } from './composables/useIconStyle';
import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCategoryView';
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
import FrameworkSelect from './components/guide/FrameworkSelect.vue';
import SnackPlayer from './components/editors/SnackPlayer.vue';
import Sandbox from './components/editors/Sandbox.vue';
const theme: Partial<Theme> = {
extends: DefaultTheme,
Layout() {
return h(DefaultTheme.Layout, null, {
'home-hero-before': () => h(HomeHeroBefore),
'sidebar-nav-before': () => h(FrameworkSelect),
'home-hero-info-before': () => h(HomeHeroInfoBefore),
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
'home-hero-image': () => h(HomeHeroIconsCard),
'home-hero-actions-after': () => h(HomeHeroAfter),
});
},
enhanceApp({ app }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
app.component('SnackPlayer', SnackPlayer)
app.component('Sandbox', Sandbox);
},
};

View File

@@ -26,6 +26,12 @@
--vp-c-text-4: rgba(60, 60, 67, 0.32);
--vp-home-hero-name-color: var(--vp-c-text);
--vp-a11y-danger: var(--vp-c-danger-3);
--vp-a11y-success: var(--vp-c-success-3);
--vp-a11y-warning: #da9200;
--vp-sidebar-input: var(--vp-c-gray-3);
}
.dark {
@@ -43,8 +49,23 @@
--vp-code-editor-string: #9ecbff;
--vp-c-text-4: rgba(235, 235, 245, 0.16);
--vp-a11y-danger: var(--vp-c-danger-2);
--vp-a11y-success: var(--vp-c-success-2);
--vp-a11y-warning: var(--vp-c-warning-2);
--vp-sidebar-input: var(--vp-c-bg-soft);
}
@view-transition {
navigation: auto;
}
/* ::view-transition-old(icons-search-box),
::view-transition-new(icons-search-box) {
animation: .3s transition 0s ease;
} */
.VPNavBarTitle .logo {
height: 36px;
width: 36px;
@@ -70,7 +91,7 @@
.VPHomeHero .container .image {
margin: 0;
order: 2;
margin-top: 32px;
/* margin-top: 32px; */
}
.VPHomeHero .container .image-container {
@@ -91,6 +112,10 @@
padding-left: 0;
}
.VPHomeHero .container {
flex-direction: column-reverse;
}
@media (min-width: 960px) {
.VPHomeHero .container .image {
order: 1;
@@ -110,60 +135,12 @@
.VPHomeHero .container .image-container {
display: block;
}
.VPHomeHero .container {
flex-direction: row;
}
}
.VPNavBarHamburger .container > span {
border-radius: 2px;
}
.sp-wrapper + * {
margin-top: 24px;
}
.sp-wrapper .sp-layout {
border-radius: 8px;
}
.sp-wrapper .sp-tabs-scrollable-container {
border-radius: 8px 8px 0 0;
position: relative;
box-shadow: inset 0 -1px var(--vp-code-tab-divider);
margin-bottom: 0px;
margin-top: -1px;
height: 48px;
padding-bottom: 1px;
}
.sp-wrapper .sp-preview-container {
background-color: transparent;
}
.sp-wrapper .sp-tabs .sp-tab-button {
padding: 0 12px;
line-height: 48px;
height: 48px;
font-size: 14px;
font-weight: 500;
position: relative;
}
.sp-wrapper .sp-tabs .sp-tab-button:after {
position: absolute;
right: 8px;
left: 8px;
bottom: 0px;
z-index: 1;
height: 1px;
content: '';
background-color: transparent;
transition: background-color 0.25s;
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true'] {
color: var(--vp-code-tab-active-text-color);
}
.sp-wrapper .sp-tabs .sp-tab-button[data-active='true']:after {
background-color: var(--vp-code-tab-active-bar-color);
}

View File

@@ -11,6 +11,12 @@ declare module '*.data.ts' {
export { data };
}
declare module '*.data' {
const data: any;
export { data };
}
declare module '*.wasm' {}
declare const resvg_wasm: RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
@@ -22,3 +28,23 @@ declare module 'node:module' {
declare module '*.node.json' {
export default IconNode;
}
declare global {
interface Window {
ExpoSnack?: {
/**
* Initialize all snack players on the page
*/
initialize(): void;
/**
* Remove a snack player container
*/
remove(container: Element): void;
/**
* Append/add a snack player container
*/
append(container: Element): void;
};
}
}

View File

@@ -2,7 +2,7 @@
title: Accessibility
---
# Accessibility
# Accessibility in dept
Icons are pictures that show what something means without using words. They can be very helpful
because they can quickly give information.
@@ -17,7 +17,7 @@ Icons are a helpful tool to improve perception, but they aren't a replacement fo
In most cases, it is probably a good idea to also provide a textual representation of your icon's
function.
![In short: Dont rely on communicating the function of elements by icons alone. Do also provide a written description of the your interactive elements. For example: write out "On this page" on your on-page navigation element.](../../images/a11y/visible-labels.svg?raw=true)
<!--@include: ../../docs/images/a11y/visible-labels.svg -->
## Contrast
@@ -27,21 +27,21 @@ with low vision or color vision deficiencies.
We recommend
following [WCAG 2.1 SC 1.4.3](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html).
![In short: use a contrast ratio of at least 4.5:1](../../images/a11y/contrast.svg?raw=true)
<!--@include: ../../docs/images/a11y/contrast.svg -->
## Use of color
Avoid relying solely on color to convey meaning in icons, as some users may have color blindness.
Instead, use additional visual cues like shape, shading or text.
![For example: Dont mark state with color, mark it with distinct visuals.](../../images/a11y/use-of-color.svg?raw=true)
<!--@include: ../../docs/images/a11y/use-of-color.svg -->
## Interactivity
Ensure that interactive icons are accessible via keyboard navigation and provide clear feedback when
activated.
![](../../images/a11y/interactivity.svg?raw=true)
<!--@include: ../../docs/images/a11y/interactivity.svg -->
In most cases this is easily done by wrapping them in icon buttons.
@@ -50,7 +50,7 @@ In most cases this is easily done by wrapping them in icon buttons.
Small targets can be difficult to click or touch, if your icon is interactive, we recommend that it
should have a minimum target size of 44×44 pixels.
![](../../images/a11y/target-size.svg?raw=true)
<!--@include: ../../docs/images/a11y/target-size.svg -->
In practice, this doesn't necessarily mean that the icon itself should be this large, only its
interactive wrapper element.
@@ -60,14 +60,14 @@ interactive wrapper element.
Icons should represent concepts or actions in a universally understandable way. Avoid using abstract
or ambiguous, or culture-specific symbols that might confuse some users.
![For example: Use universally understandable symbols and don't base your choice of icon on puns.](../../images/a11y/meaningfulness.svg?raw=true)
<!--@include: ../../docs/images/a11y/meaningfulness.svg -->
## Consistency
Maintain consistency in icon design and usage across your interface to help users learn and
understand their meanings more easily.
![For example: Dont use the same icon for multiple distinct purposes or meanings. Dont use different icons for the same purpose or function.](../../images/a11y/consistency.svg?raw=true)
<!--@include: ../../docs/images/a11y/consistency.svg -->
## Text Alternatives
@@ -89,7 +89,7 @@ In case some of your icons stand alone, and they serve a non-decorative function
provide the appropriate accessible label for them.
:::
![In short: provide accessible label for semantic icons, but not for decorative icons.](../../images/a11y/alttext-standalone.svg?raw=true)
<!--@include: ../../docs/images/a11y/alttext-standalone.svg -->
In general try to avoid using functional icons with no interactivity, we recommend that:
@@ -104,7 +104,8 @@ elements (badges, buttons, nav items etc.) only, _not_ the icons themselves.
Do not provide an accessible label to icons when used on a button, as this label will be read out by
screen readers, leading to nonsensical text.
![](../../images/a11y/alttext-buttons.svg?raw=true)
<!--@include: ../../docs/images/a11y/alttext-buttons.svg -->
::: details Code examples
@@ -132,7 +133,7 @@ the close button of a dialog for example).
As previously stated, you should provide your accessible label on the icon button itself, not the
contained icon.
![](../../images/a11y/alttext-iconbuttons.svg?raw=true)
<!--@include: ../../docs/images/a11y/alttext-iconbuttons.svg -->
::: details Code examples
@@ -215,3 +216,16 @@ We also recommend checking out the following resources about accessibility:
- [A11yTalks](https://www.a11ytalks.com/)
- [A11y automation tracker](https://a11y-automation.dev/)
- [The A11Y Project](https://www.a11yproject.com/)
<style>
svg.a11y-example {
max-width: calc(100% + 48px);
margin: 0 -24px;
}
@media (min-width: 480px) {
svg.a11y-example {
margin: 0;
max-width: 100%;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -64,25 +64,24 @@ Implementation of the lucide icon library for Vue applications.
::: code-group
```sh [pnpm]
pnpm add lucide-vue-next
pnpm add @lucide/vue
```
```sh [yarn]
yarn add lucide-vue-next
yarn add @lucide/vue
```
```sh [npm]
npm install lucide-vue-next
npm install @lucide/vue
```
```sh [bun]
bun add lucide-vue-next
bun add @lucide/vue
```
:::
For more details, see the [documentation](packages/lucide-vue-next.md).
For Vue 2 use the `lucide-vue` package.
For more details, see the [documentation](packages/lucide-vue.md).
## Svelte
@@ -91,22 +90,22 @@ Implementation of the lucide icon library for Svelte applications.
::: code-group
```sh [pnpm]
pnpm add lucide-svelte
pnpm add @lucide/svelte
```
```sh [yarn]
yarn add lucide-svelte
yarn add @lucide/svelte
```
```sh [npm]
npm install lucide-svelte
npm install @lucide/svelte
```
```sh [bun]
bun add lucide-svelte
bun add @lucide/svelte
```
:::
> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package.
For more details, see the [documentation](packages/lucide-svelte.md).
@@ -248,13 +247,3 @@ The lucide figma plugin.
Visit [Figma community page](https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons) to install the plugin.
![Setting Page Size](https://www.figma.com/community/plugin/939567362549682242/thumbnail 'Figma Lucide Cover')
## Flutter
Implementation of Lucide icon library for Flutter applications.
```bash
flutter pub add lucide_icons
```
For more details, see the [pub.dev](https://pub.dev/packages/lucide_icons).

View File

@@ -1,111 +0,0 @@
# Lucide React
React components for Lucide icons that integrate seamlessly into your React applications. Each icon is a fully-typed React component that renders as an optimized inline SVG, giving you the flexibility of components with the performance of vector graphics.
**What you can accomplish:**
- Import icons as React components with full TypeScript support
- Pass props to customize size, color, stroke width, and other SVG attributes
- Use icons in JSX with the same ease as any other React component
- Benefit from automatic tree-shaking to include only the icons you use
- Create dynamic icon components that respond to state and user interactions
## Installation
::: code-group
```sh [pnpm]
pnpm add lucide-react
```
```sh [yarn]
yarn add lucide-react
```
```sh [npm]
npm install lucide-react
```
```sh [bun]
bun add lucide-react
```
:::
## How to use
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon can be imported as a React component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
### Example
Additional props can be passed to adjust the icon:
```jsx
import { Camera } from 'lucide-react';
// Usage
const App = () => {
return <Camera color="red" size={48} />;
};
export default App;
```
## Props
| name | type | default |
| --------------------- | --------- | ------------ |
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `strokeWidth` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
### Applying props
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```jsx
// Usage
const App = () => {
return <Camera size={48} fill="red" />;
};
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-react';
import { coconut } from '@lucide/lab';
const App = () => (
<Icon iconNode={coconut} />
);
```
## Dynamic Icon Component
It is possible to create one generic icon component to load icons. But it is not recommended, since it is importing all icons during the build. This increases build time and the different modules it will create.
`DynamicIcon` is useful for applications that want to show icons dynamically by icon name. For example, when using a content management system with where icon names are stored in a database.
For static use cases, it is recommended to import the icons directly.
The same props can be passed to adjust the icon appearance. The `name` prop is required to load the correct icon.
```jsx
import { DynamicIcon } from 'lucide-react/dynamic';
const App = () => (
<DynamicIcon name="camera" color="red" size={48} />
);
```

View File

@@ -30,14 +30,7 @@ This package includes the following implementations of Lucide icons:
SVG sprites and icon fonts include **all icons**, which can significantly increase your app's bundle size and load time.
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using:
- [lucide](lucide)
- [lucide-react](lucide-react)
- [lucide-vue](lucide-vue)
- [lucide-vue-next](lucide-vue-next)
- [lucide-angular](lucide-angular)
- [lucide-preact](lucide-preact)
For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using one of the framework-specific [packages](../../packages).
:::
## Installation

View File

@@ -1,148 +0,0 @@
# Lucide Vue Next
Vue 3 components for Lucide icons that leverage the Composition API and modern Vue features. Each icon is a reactive Vue component that renders as an inline SVG, providing excellent performance and developer experience in Vue 3 applications.
**What you can accomplish:**
- Use icons as Vue 3 components with full reactivity and TypeScript support
- Bind icon properties to reactive data and computed values
- Customize icons with props, slots, and Vue's powerful templating system
- Integrate seamlessly with Vue 3's Composition API and script setup syntax
- Build dynamic interfaces where icons respond to application state changes
## Installation
::: code-group
```sh [pnpm]
pnpm add lucide-vue-next
```
```sh [yarn]
yarn add lucide-vue-next
```
```sh [npm]
npm install lucide-vue-next
```
```sh [bun]
bun add lucide-vue-next
```
:::
## How to use
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon can be imported as a Vue component, which renders an inline SVG Element. This way only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
### Example
You can pass additional props to adjust the icon.
```vue
<script setup>
import { Camera } from 'lucide-vue-next';
</script>
<template>
<Camera
color="red"
:size="32"
/>
</template>
```
## Props
| name | type | default |
| ----------------------- | --------- | ------------ |
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `stroke-width` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
| `default-class` | *string* | lucide-icon |
### Applying props
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```vue
<template>
<Camera fill="red" />
</template>
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```vue
<script setup>
import { Icon } from 'lucide-vue-next';
import { baseball } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="baseball" />
</template>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.
::: danger
The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`.
:::
### Icon Component Example
```vue
<script setup>
import { computed } from 'vue';
import * as icons from "lucide-vue-next";
const props = defineProps({
name: {
type: String,
required: true
},
size: Number,
color: String,
strokeWidth: Number,
defaultClass: String
})
const icon = computed(() => icons[props.name]);
</script>
<template>
<component
:is="icon"
:size="size"
:color="color"
:stroke-width="strokeWidth" :default-class="defaultClass"
/>
</template>
```
### Using the Icon Component
All other props listed above also work on the `Icon` Component.
```vue
<template>
<div id="app">
<Icon name="Airplay" />
</div>
</template>
```

View File

@@ -9,28 +9,24 @@ Vue 2 components for Lucide icons that integrate with Vue's Options API and temp
- Build applications using Vue 2's familiar syntax and patterns
- Bridge the gap while planning migration to Vue 3
::: danger
This package is deprecated. Vue 2 is EOF See [Announcement](https://v2.vuejs.org/eol/). Migrate to Vue 3.
:::
## Installation
::: code-group
```sh [pnpm]
pnpm add lucide-vue
pnpm add @lucide/vue
```
```sh [yarn]
yarn add lucide-vue
yarn add @lucide/vue
```
```sh [npm]
npm install lucide-vue
npm install @lucide/vue
```
```sh [bun]
bun add lucide-vue
bun add @lucide/vue
```
:::
@@ -43,21 +39,19 @@ Each icon can be imported as a Vue component, which renders an inline SVG Elemen
### Example
Additional props can be passed to adjust the icon:
You can pass additional props to adjust the icon.
```vue
<template>
<Camera color="red" :size="32" />
</template>
<script>
import { Camera } from 'lucide-vue';
export default {
name: 'My Component',
components: { Camera }
};
<script setup>
import { Camera } from '@lucide/vue';
</script>
<template>
<Camera
color="red"
:size="32"
/>
</template>
```
## Props
@@ -80,6 +74,28 @@ To customize the appearance of an icon, you can pass custom properties as props
</template>
```
## With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
### Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```vue
<script setup>
import { Icon } from '@lucide/vue';
import { baseball } from '@lucide/lab';
</script>
<template>
<Icon :iconNode="baseball" />
</template>
```
## One generic icon component
It is possible to create one generic icon component to load icons, but it is not recommended.
@@ -91,30 +107,37 @@ The example below imports all ES Modules, so exercise caution when using it. Imp
### Icon Component Example
```vue
<template>
<component :is="icon" />
</template>
<script setup>
import { computed } from 'vue';
import * as icons from "@lucide/vue";
<script>
import * as icons from 'lucide-vue';
const props = defineProps({
name: {
type: String,
required: true
},
size: Number,
color: String,
strokeWidth: Number,
defaultClass: String
})
export default {
props: {
name: {
type: String,
required: true
}
},
computed: {
icon() {
return icons[this.name];
}
}
};
const icon = computed(() => icons[props.name]);
</script>
<template>
<component
:is="icon"
:size="size"
:color="color"
:stroke-width="strokeWidth" :default-class="defaultClass"
/>
</template>
```
#### Using the Icon Component
### Using the Icon Component
All other props listed above also work on the `Icon` Component.
```vue
<template>

View File

@@ -0,0 +1,39 @@
<script setup>
import OverviewLink from '../../../.vitepress/theme/components/base/OverviewLink.vue'
</script>
# Accessibility
By default all lucide icons are applied with `aria-hidden="true"` which makes them **not** accessible for screen readers.
This is done because most of the time icons are used for decorative purposes only.
If you need to make an icon accessible, you can do so by passing a `title` element as a child or passing the `aria-label` prop to the icon component.
This will remove the `aria-hidden` attribute and make the icon accessible.
```tsx
<House>
<title>This is my house</title>
</House>
// or
<House aria-label="This is my house" />
```
We recommend to describe the icon in a way that makes sense for the user, or the action it represents and that makes sense in the context of your application.
## Accessible Icon Buttons
When using icon buttons, you should not provide an accessible label on the icon itself, but rather on the button.
```tsx
<button aria-label="Go to home">
<House />
</button>
```
## Detailed Guide on Accessibility
For best practices on how to use icons accessibly in your application, please refer to our detailed guide on accessibility.
<OverviewLink href="/guide/accessibility" title="Accessible Icons" desc="Best practices for accessible icons in your application."/>

View File

@@ -0,0 +1,55 @@
# Aliased Names
Icons can have multiple names for the same icon. This is because we choose to rename some icons to make them more consistent with the rest of the icon set, or the name was not generic. For example, the `edit-2` icon is renamed to `pen` to make the name more generic, since it is just a pen icon.
Beside aliases names lucide also includes prefixed and suffixed names to use within your project. This is to prevent import name collisions with other libraries or your own code.
```tsx
// These are all the same icon
import {
House,
HouseIcon,
LucideHouse,
} from "lucide-preact";
```
## Choosing import name style
To be consistent in your imports or want to change the autocompletion of Lucide icons in your IDE there an option to able the choose the import name style you want.
This can be done by creating a custom module declaration file to override the lucide imports and turning off the autocomplete in your IDE.
### Turn off autocomplete in your IDE
```json [.vscode/settings.json]
{
"typescript.preferences.autoImportFileExcludePatterns": [
"lucide-preact",
]
}
```
### Create a custom module declaration file
This will enable you to choose the import name style you want to use in your project.
```ts [React]
declare module "lucide-preact" {
// Prefixed import names
export * from "lucide-preact/dist/lucide-preact.prefixed";
// or
// Suffixed import names
export * from "lucide-preact/dist/lucide-preact.suffixed";
}
```
Place this in your project root or in a folder where your tsconfig.json is located, or locate it in your defined type directory.
Easiest way is to create a `@types` folder in your project root and name the file `lucide-preact.d.ts`.
### Import name styles
| Import Style | Available imports | Declaration file import |
| ------------- | --------------------------- | ----------------------- |
| Default | Home, HomeIcon, LucideHome | |
| Prefixed | LucideHome | lucide-preact.prefixed |
| Suffixed | HomeIcon | lucide-preact.suffixed |

View File

@@ -0,0 +1,62 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import combineIconsExample from './examples/combining-icons/files.ts'
import combineCustomExample from './examples/combining-icons-custom/files.ts'
import combineNotificationExample from './examples/combining-icons-notification/files.ts'
</script>
# Combining icons
You can combine multiple icons into a single icon by using SVG in SVG.
This is useful for if you want to be creative and make your own custom icons by combining existing icons.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="combineIconsExample"
:options="{
editorHeight: 400,
editorWidthPercentage: 60,
}"
/>
This is valid SVG and all SVG properties are supported on the icons.
The `x` and `y` coordinates can be adjusted to position the icons as you like.
## Caveats
When combining icons, you need to make sure that the icon you is in the `viewBox` of the outer icon (24x24).
## With custom SVG elements
You can also use SVG elements to create your own icons.
## Example with notification badge
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="combineNotificationExample"
:options="{
editorHeight: 480,
editorWidthPercentage: 60,
}"
/>
## Example with text element
You can also use the `text` SVG element to add text to your icon.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="combineCustomExample"
:options="{
editorHeight: 480,
editorWidthPercentage: 60,
}"
/>

View File

@@ -0,0 +1,22 @@
import { File } from "lucide-preact";
import { h } from "preact";
function App() {
return (
<div className="app">
<File size={48}>
<text
x={7.5}
y={19}
font-size={8}
font-family="Verdana,sans-serif"
stroke-width={1}
>
JS
</text>
</File>
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,24 @@
import { Mail } from "lucide-preact";
import { h } from "preact";
function App() {
const hasUnreadMessages = true;
return (
<div className="app">
<Mail size={48}>
{hasUnreadMessages && (
<circle
r="3"
cx="21"
cy="5"
stroke="none"
fill="#F56565"
/>
)}
</Mail>
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,19 @@
import { Scan, User } from "lucide-preact";
import { h } from "preact";
function App() {
return (
<div className="app">
<Scan size={48}>
<User
size={12}
x={6}
y={6}
absoluteStrokeWidth
/>
</Scan>
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,24 @@
import { Star, StarHalf } from "lucide-preact";
import { h } from "preact";
import "./icon.css";
function App() {
return (
<div className="app">
<div className="star-rating">
<div className="stars">
{ Array.from({ length: 5 }, () => (
<Star fill="#111" strokeWidth={0} />
))}
</div>
<div className="stars rating">
<Star fill="yellow" strokeWidth={0} />
<Star fill="yellow" strokeWidth={0} />
<StarHalf fill="yellow" strokeWidth={0} />
</div>
</div>
</div>
);
}
export default App;

View File

@@ -0,0 +1,17 @@
import App from './App.js?raw'
import IconCss from './icon.css?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'App.js': {
code: App,
active: true,
},
'icon.css': {
code: IconCss,
readOnly: false,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,31 @@
import {
TentTree,
Caravan,
FlameKindling,
MountainSnow,
Trees,
Axe,
Map,
CloudMoon,
Sparkles,
} from "lucide-preact";
import { h } from "preact";
import "./icon.css";
function App() {
return (
<div className="grid">
<TentTree />
<Caravan />
<FlameKindling />
<MountainSnow />
<Trees />
<Axe />
<Map />
<CloudMoon />
<Sparkles />
</div>
);
}
export default App;

View File

@@ -0,0 +1,17 @@
import App from './App.js?raw'
import IconCss from './icon.css?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,16 @@
.lucide {
width: 48px;
height: 48px;
stroke-width: 1.5;
}
.lucide * {
vector-effect: non-scaling-stroke;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 6px;
}

View File

@@ -0,0 +1,31 @@
import {
CakeSlice,
Candy,
Apple,
Cookie,
Martini,
IceCream2,
Sandwich,
Wine,
Dessert,
} from "lucide-preact";
import { h } from "preact";
import "./icon.css";
function App() {
return (
<div className="grid">
<CakeSlice />
<Candy />
<Apple />
<Cookie />
<Martini />
<IceCream2 />
<Sandwich />
<Wine />
<Dessert />
</div>
);
}
export default App;

View File

@@ -0,0 +1,17 @@
import App from './App.js?raw'
import IconCss from './icon.css?raw'
import { preactFiles } from '../../../basics/examples/files'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,14 @@
.lucide {
/* Change this! */
color: #ffadff;
width: 48px;
height: 48px;
stroke-width: 1px;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-template-rows: 1fr 1fr 1fr;
gap: 6px;
}

View File

@@ -0,0 +1,33 @@
---
head:
- - link
- rel: canonical
href: https://lucide.dev/guide/vue/advanced/filled-icons
---
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import sizeIconExample from './examples/filled-icon-example/files.ts'
</script>
# Filled Icons
Fills are officially not supported.
However, all SVG properties are available on all icons.
Fill can still be used and will work fine on certain icons.
Example with stars:
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="sizeIconExample"
:options="{
editorHeight: 480,
editorWidthPercentage: 60,
}"
/>
## Will Lucide have fills in the future?
This feature has been requested several times and discussion is happening at [#458](https://github.com/lucide-icons/lucide/discussions/458).

View File

@@ -0,0 +1,70 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import globalIconCssExample from './examples/global-styling-css-example/files.ts'
import globalAbsoluteStrokewidthExample from './examples/global-styling-absolute-strokewidth-example/files.ts'
</script>
# Global Styling
Adjusting icons can be done by using [color](../basics/color.md), [size](../basics/sizing.md) and [stroke width](../basics/stroke-width.md).
To style all icons globally, you can either use CSS, or use a context provider.
We recommend using CSS for global styling, as it is the most straightforward way to achieve this.
But using CSS prevents you from using props like `size`, `color` and `strokeWidth` on individual icons, since CSS specificity will override these props, to be able to use the props on individual ones you need to use the Lucide context provider.
## Context Provider
For global styling using a context provider, you can use the `LucideProvider` component that is provided by the `lucide-preact` package.
```tsx
import { LucideProvider, Home } from 'lucide-preact';
const App = () => (
<LucideProvider
color="red"
size={48}
strokeWidth={2}
>
<Home />
</LucideProvider>
);
```
This will apply the `color`, `size` and `strokeWidth` props to all icons that are children of the `LucideProvider`.
## Style by using CSS
Styling icons is easy to accomplish using CSS.
Every icon has a class attribute applied called `lucide`. This class name can be used in the CSS file to target all icons that are being used within the app.
- The **color** of the icons can be changed using the [`color`](https://developer.mozilla.org/en-US/docs/Web/CSS/color) CSS property.
- The **size** of the icons can be changed using [`width`](https://developer.mozilla.org/en-US/docs/Web/CSS/width) and [`height`](https://developer.mozilla.org/en-US/docs/Web/CSS/height) CSS properties.
- The **stroke width** of the icons can be changed using the [`stroke-width`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width) CSS property.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="globalIconCssExample"
:options="{
eeditorHeight: 480,
editorWidthPercentage: 60,
}"
/>
### Absolute stroke width
For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="globalAbsoluteStrokewidthExample"
:options="{
editorHeight: 480,
editorWidthPercentage: 60,
}"
/>

View File

@@ -0,0 +1,93 @@
# TypeScript Support
List of exported types from the `lucide-preact` package.
These can be used to type your components when using Lucide icons in a TypeScript React project
## `LucideProps`
Exports all props that can be passed to an icon component and any other SVG attributes, see: [SVG Presentation Attributes on MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```ts
interface LucideProps {
size?: number | string;
color?: string;
strokeWidth?: number;
absoluteStrokeWidth?: boolean;
[key: string]: any; // Any other SVG attributes
}
```
### Using `LucideProps`
You can use the `LucideProps` interface to type your custom icon components or when you need to work with icon props.
```tsx
import { type LucideProps } from 'lucide-preact';
import { Camera } from 'lucide-preact';
const WrapIcon = (props: LucideProps) => {
return <Camera {...props} />;
};
export default WrapIcon;
```
## `LucideIcon`
Type for individual icon components.
```ts
type LucideIcon = React.FC<LucideProps>;
```
### Using `LucideIcon`
You can use the `LucideIcon` type when you need to work with icon components directly.
```tsx
import { type LucideIcon } from 'lucide-preact';
interface ButtonProps {
icon: LucideIcon;
label: string;
}
const IconButton = ({ icon: Icon, label }: ButtonProps) => {
return (
<button aria-label={label}>
<Icon size={16} />
</button>
);
};
export default IconButton;
```
## `IconNode`
Type for the raw SVG structure of an icon. This is an array of SVG elements and their attributes to render the icon.
Not commonly used directly in application code. But can be useful for advanced use cases, such as using custom icons or with Lucide lab.
```ts
type IconNode = [elementName: string, attrs: Record<string, string | number>][];
```
### Using `IconNode`
You can use the `IconNode` type when you need to work with the raw SVG structure of an icon.
```tsx
import { type IconNode, Icon } from 'lucide-preact';
const customIcon: IconNode = [
['circle', { cx: 12, cy: 12, r: 10 }],
['line', { x1: 12, y1: 8, x2: 12, y2: 12 }],
['line', { x1: 12, y1: 16, x2: 12, y2: 16 }],
];
const MyCustomIcon = () => {
return (
<Icon iconNode={customIcon} size={24} color="blue" />
);
};
export default MyCustomIcon;
```

View File

@@ -0,0 +1,19 @@
# With Lucide lab or custom icons
[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library.
They can be used by using the `Icon` component.
All props like regular lucide icons can be passed to adjust the icon appearance.
## Using the `Icon` component
This creates a single icon based on the iconNode passed and renders a Lucide icon component.
```jsx
import { Icon } from 'lucide-preact';
import { coconut } from '@lucide/lab';
const App = () => (
<Icon iconNode={coconut} />
);
```

View File

@@ -0,0 +1,42 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import buttonExampleFiles from './examples/button-example/files.ts'
import iconColorExampleFiles from './examples/color-icon/files.ts'
</script>
# Color
By default, all icons have the color value: `currentColor`. This keyword uses the element's computed text `color` value to represent the icon color.
Read more about [ `currentColor` on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword).
## Adjust the color using the `color` prop
The color can be adjusted by passing the color prop to the element.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="iconColorExampleFiles"
:options="{
editorHeight: 320,
editorWidthPercentage: 60,
}"
/>
## Using parent elements text color value
Because the color of lucide icons uses `currentColor`, the color of the icon depends on the computed `color` of the element, or it inherits it from its parent.
For example, if a parent element's color value is `#fff` and one of the children is a lucide icon, the color of the icon will be rendered as `#fff`. This is browser native behavior.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="buttonExampleFiles"
:options="{
editorHeight: 340,
editorWidthPercentage: 60,
}"
/>

View File

@@ -0,0 +1,15 @@
import { h } from "preact";
import { RollerCoaster } from "lucide-preact";
function App() {
return (
<div className="app">
<RollerCoaster
size={96}
absoluteStrokeWidth={true}
/>
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,6 @@
import { h } from "preact";
import Button from "./Button";
export default function App() {
return <Button />;
}

View File

@@ -0,0 +1,13 @@
import { h } from "preact";
import { ThumbsUp } from "lucide-preact";
function LikeButton() {
return (
<button style={{ color: "#fff" }}>
<ThumbsUp />
Like
</button>
);
}
export default LikeButton;

View File

@@ -0,0 +1,18 @@
import App from './App.js?raw'
import Button from './Button.jsx?raw'
import { preactFiles } from '../files'
const files = {
'App.js': {
code: App,
hidden: true
},
'Button.jsx': {
code: Button,
active: true,
readOnly: false,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,12 @@
import { h } from "preact";
import { Smile } from "lucide-preact";
function App() {
return (
<div className="app">
<Smile color="#3e9392" />
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../files.ts'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,23 @@
import index from './index.js?raw'
import packageJson from './package.json?raw'
import indexHtml from './index.html?raw'
import styles from './styles.css?raw'
export const preactFiles = {
'index.js': {
code: index,
hidden: true
},
'package.json': {
code: packageJson,
hidden: true
},
'index.html': {
code: indexHtml,
hidden: true
},
'styles.css': {
code:styles,
hidden: true
},
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
</body>
</html>

View File

@@ -0,0 +1,7 @@
import { render, h } from "preact";
import App from "./App";
import "./styles.css"
if (typeof window !== "undefined") {
render(<App />, document.getElementById("root"));
}

View File

@@ -0,0 +1,17 @@
{
"main": "index.js",
"scripts": {
"start": "npm run -s dev",
"build": "preact build",
"serve": "preact build && preact serve",
"dev": "preact watch"
},
"devDependencies": {
"preact-cli": "^1.4.1"
},
"dependencies": {
"preact": "10.11.3",
"preact-compat": "3.19.0",
"lucide-preact": "latest"
}
}

View File

@@ -0,0 +1,13 @@
import { h } from "preact";
import { Beer } from "lucide-preact";
import "./icon.css";
function App() {
return (
<div className="app">
<Beer className="my-beer-icon" />
</div>
);
}
export default App;

View File

@@ -0,0 +1,17 @@
import App from './App.js?raw'
import IconCss from './icon.css?raw'
import { preactFiles } from '../files'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,12 @@
import { h } from "preact";
import { Landmark } from "lucide-preact";
function App() {
return (
<div className="app">
<Landmark size={64} />
</div>
);
}
export default App;

View File

@@ -0,0 +1,13 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
import { preactFiles } from '../files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,14 @@
import { h } from "preact";
import { Star } from "lucide-preact";
import "./icon.css";
function App() {
return (
<div className="text-wrapper">
<Star class="my-icon" />
<div>Yes</div>
</div>
);
}
export default App;

View File

@@ -0,0 +1,18 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
import IconCss from './icon.css?raw'
import { preactFiles } from '../files'
const files = {
'icon.css': {
code: IconCss,
readOnly: false,
active: true,
},
'App.js': {
code: App,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,12 @@
import { h } from "preact";
import { PartyPopper } from "lucide-preact";
function App() {
return (
<div>
<PartyPopper className="w-24 h-24" />
</div>
);
}
export default App;

View File

@@ -0,0 +1,13 @@
import App from './App.js?raw'
import styles from '../styles.css?raw'
import { preactFiles } from '../files'
const files = {
'App.js': {
code: App,
active: true
},
...preactFiles,
}
export default files

View File

@@ -0,0 +1,12 @@
import { h } from "preact";
import { FolderLock } from "lucide-preact";
function App() {
return (
<div className="app">
<FolderLock strokeWidth={1} />
</div>
);
}
export default App;

View File

@@ -0,0 +1,12 @@
import App from './App.js?raw'
import { preactFiles } from '../files'
const files = {
'App.js': {
code: App,
active: true,
},
...preactFiles
}
export default files

View File

@@ -0,0 +1,67 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import sizeIconExample from './examples/size-icon-example/files.ts'
import sizeIconCssExample from './examples/size-icon-css-example/files.ts'
import sizeIconFontExample from './examples/size-icon-font-example/files.ts'
import sizeIconTailwind from './examples/size-icon-tailwind-example/files.ts'
</script>
# Sizing
By default, the size of all icons is `24px` by `24px`. The size is adjustable using the `size` prop and CSS.
## Adjusting the icon size using the `size` prop
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="sizeIconExample"
:options="{
editorHeight: 320,
editorWidthPercentage: 60,
}"
/>
## Adjusting the icon size via CSS
The CSS properties `width` and `height` can be used to adjust the icon size.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="sizeIconCssExample"
:options="{
editorHeight: 320,
}"
/>
### Dynamically change the icon size based on the font size
It is possible to resize icons based on font size. This can be achieved using the `em` unit. See this [MDN article](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#ems) for more information on the `em` unit.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="sizeIconFontExample"
:options="{
editorHeight: 320,
}"
/>
### Resizing with Tailwind
`size-*` utilities can be used to adjust the size of the icon. See the [Tailwind documentation](https://tailwindcss.com/docs/width#setting-both-width-and-height) for more information on the `size-*` utilities.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="sizeIconTailwind"
:options="{
externalResources: ['https://cdn.tailwindcss.com'],
editorHeight: 300,
editorWidthPercentage: 60,
}"
/>
<!-- Code Example -->

View File

@@ -0,0 +1,50 @@
<script setup>
import { Sandpack } from 'sandpack-vue3'
import sandpackTheme from '../../../.vitepress/theme/sandpackTheme.json'
import strokeWidth from './examples/stroke-width-icon/files.ts'
import absoluteStrokeWidth from './examples/absolute-stroke-width-icon/files.ts'
</script>
# Stroke width
All icons are designed with SVG elements using strokes.
These have a default stroke width of `2px`.
The `strokeWidth` can be adjusted to create a different look of the icons.
## Adjusting stroke width with `strokeWidth` prop
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="strokeWidth"
:options="{
editorHeight: 320,
editorWidthPercentage: 60,
}"
/>
## Absolute stroke width
When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `absoluteStrokeWidth` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon.
This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen.
Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes.
![Absolute stroke width comparison](../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison")
### Adjusting stroke width with `absoluteStrokeWidth` prop
Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute.
<Sandpack
template="vanilla"
:theme="sandpackTheme"
:files="absoluteStrokeWidth"
:options="{
editorHeight: 340,
editorWidthPercentage: 60,
}"
/>

View File

@@ -0,0 +1,79 @@
<script setup lang="ts">
import OverviewLink from '../../.vitepress/theme/components/base/OverviewLink.vue'
import OverviewLinkGrid from '../../.vitepress/theme/components/base/OverviewLinkGrid.vue'
import { preactSidebar } from '../../.vitepress/sidebar/preact'
</script>
# Getting started
This guide will help you get started with Lucide in your Preact project.
Make sure you have a Preact environment set up. If you don't have one yet, you can create a new Preact project using Create Preact App, Vite, or any other Preact boilerplate of your choice.
## Installation
::: code-group
```sh [pnpm]
pnpm install lucide-preact
```
```sh [yarn]
yarn add lucide-preact
```
```sh [npm]
npm install lucide-preact
```
```sh [bun]
bun add lucide-preact
```
:::
## Importing your first icon
Lucide is built with ES Modules, so it's completely tree-shakable.
Each icon can be imported as a Preact component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away.
### Example
Additional props can be passed to adjust the icon:
```jsx
import { Camera } from 'lucide-preact';
// Usage
const App = () => {
return <Camera color="red" size={48} />;
};
export default App;
```
## Props
| name | type | default |
| --------------------- | --------- | ------------ |
| `size` | *number* | 24 |
| `color` | *string* | currentColor |
| `strokeWidth` | *number* | 2 |
| `absoluteStrokeWidth` | *boolean* | false |
### Applying props
To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation).
```jsx
// Usage
const App = () => {
return <Camera size={48} fill="red" />;
};
```
More examples and details how to use props, continue the guide:
<OverviewLinkGrid>
<OverviewLink v-for="item in preactSidebar[1].items" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
</OverviewLinkGrid >

View File

@@ -0,0 +1,53 @@
---
title: Overview
nextPage:
- getting-started
---
<script setup>
import OverviewLink from '../../.vitepress/theme/components/base/OverviewLink.vue'
import OverviewLinkGrid from '../../.vitepress/theme/components/base/OverviewLinkGrid.vue'
import { preactSidebar } from '../../.vitepress/sidebar/preact'
</script>
<img src="/package-logos/dark/lucide-preact.svg" alt="Lucide icon library for Preact applications." width="540" style="margin-bottom: 48px;"/>
<!-- [![npm](https://img.shields.io/npm/v/lucide-preact?color=blue)](https://www.npmjs.com/package/lucide-preact)
[![NPM Downloads](https://img.shields.io/npm/dw/lucide-preact)](https://www.npmjs.com/package/lucide-preact) -->
# Lucide for Preact
Lucide provides a Preact icon component library that makes it easy to integrate icons into your Preact applications.
Each icon is available as a standalone Preact component, allowing for seamless integration and customization.
List of features:
- **Easy to Use**: Import icons as Preact components and use them directly in your Preact components with JSX.
- **Customizable**: Adjust size, color, and other properties via props.
- **Tree-shakable**: Only the icons you use are included in your final bundle
- **TypeScript Support**: Fully typed components for better developer experience.
## Overview
<OverviewLink href="/guide/preact/getting-started" title="Getting Started" desc="Learn how to get started with Lucide in your Preact project."/>
### Basics
{{''}}
<OverviewLinkGrid>
<OverviewLink v-for="item in preactSidebar[1].items" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
</OverviewLinkGrid >
### Advanced
{{''}}
<OverviewLinkGrid>
<OverviewLink v-for="item in preactSidebar[2].items" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
</OverviewLinkGrid >
### Resources
{{''}}
<OverviewLinkGrid>
<OverviewLink v-for="item in preactSidebar[3].items" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
</OverviewLinkGrid >

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