docs(version-1): Version 1 website (#4142)

* Update images

* Adjust color

* update accessebility

* Add matrix hero

* Add typescript documentation

* Add basics section to vue pages

* Add svelte docs

* Fix dynamic sidebar

* Add animation?

* Some adjustments

* Adjust animation

* Updates docs

* Add scaledown animation

* Add docs for vue types

* Fix layout accessibility page

* fix framework select

* adjust easing home animation

* Write docs for Vue

* Adjust animation home

* Adjust home hero animation

* Finish svelte docs

* Add solid pages

* remove spiral animation component

* Add group icons

* Added solid docs

* update preact docs

* Adjust examples preact docs

* Add snackplayer

* Get new editor

* Save this

* Add examples

* Adjust styling

* setup custom sandpack

* Add script

* Format files

* Make sandpack plugin work

* migrate react docs

* Fix svelte and solid examples

* Migrate to solid packages

* Add darkmode package logos

* Fix bug in selector

* migrate vue examples

* migrate advanced vue items

* migrate preact and svelte examples

* Add astro docs

* adding more docs on guide for lucide library

* Fix home animation

* Cleanup

* Added resources page, with more details and content

* Add hero badge for Version 1

* Fix vercel json

* Update missing paths

* Fix build?

* Replace lucide-vue-next

* Fix build

* Add some docs

* update markdown

* Setup angular docs

* Add basic angular docs

* Adjust code examples

* Update title and descriptions

* Update accessibility link

* Update title and description

* Add og image

* Fix alignment

* Add migration guides

* Adjust version 1 markdown file

* Add migration guide to the main list

* Minor fixes

* Update docs

* Apply feedback

* Select icons

* Add pointer events none

* Fix package links homepage

* Format code

* Fix types

* Fix focus

* Fix build

* Fix focus

* Apply feedback

* Adjust imports

* Adjust imports

* Fix search

* Apply feedback

* Fix import in font docs

* Small fixes

* docs(guide): added easter egg icon. stay tuned, guys 😉

* Add llm txt plugin

* Adjust logo top

* docs(version-1): upgraded @lucide/angular guide for v1 website (#4144)

* docs(guide/angular): rewrite some angular documentation

* docs(guide/angular): extens angular guide with combining icons & icon provider guides

* docs(guide/angular): fix angular sandbox, still needs @lucide/angular release tho

* docs(guide/angular): rework the getting started page a bit more, refactor some more prop=>input occurences

* docs(guide/angular): context provider => provideLucideConfig

* Update docs/guide/angular/migration.md

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>

* Update docs/guide/angular/advanced/combining-icons.md

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>

* Apply suggestions from code review

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

* docs(guide/angular): rework a11y for better flow & pacing

* docs(guide/angular): salvage original phrase, it has better flow

* docs(guide/angular): some more a11y nitpicking

* docs(guide/angular): reduce llm fatigue

* docs(guides/angular): fix app component selector

* docs(guides/angular): fix angular sandpacks

* Add sandpack angular to improve tree-shakable stack

* Update docs/guide/angular/getting-started.md

* docs(guides/angular): fix user import in combinding icons guide

* docs(guides/angular): fix nested SVG phrasing

* Update docs/guide/angular/advanced/with-lucide-lab.md

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

* Update docs/guide/angular/advanced/filled-icons.md

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

* docs(guides/angular): fix createLucideIcon guide

* docs(guides/angular): upgrade all relevant angular sandpack demos

---------

Co-authored-by: Eric Fennis <eric.fennis@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(docs): add autogenerated OG images to gitignore

* fix(docs): unify the case of "Lucide" & "Lucide Lab" over all docs and readmes

* Fix build

* Add next tag to all installation guides

* Add top bar notification

* Minor fixes

* Update text

* Add todos

* Update og image

* Adjust install command

* Color top bar

* fix(docs): ignore all OG images, expect general.png

* fix(docs): fix the package list, hide @lucide/icons, downgrade angular logo, link to v0 guide, fix package title alignment

* Update docs/.vitepress/theme/components/base/LayoutTop.vue

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

* Update docs/guide/react/advanced/dynamic-icon-component.md

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

* Update CONTRIBUTING.md

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

* Update docs/.vitepress/sidebar/resources.ts

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

* Fix code example

* fix(docs): version 1 => Version 1

* feat(docs): add angular to new features in v1

* Fix next line issue

* feat(docs): fix typo (code example_s_)

* Fix deadlink

* feat(docs): fix some more typos and irky grammar

* feat(docs): fix lowercase lucide in sidebar 😅

* Update docs/guide/version-1.md

Co-authored-by: Karsa <contact@karsa.org>

---------

Co-authored-by: Karsa <contact@karsa.org>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Eric Fennis
2026-03-20 17:04:28 +01:00
committed by GitHub
parent e715245d62
commit 484f2c9cd3
314 changed files with 15456 additions and 2313 deletions

20
.github/labeler.yml vendored
View File

@@ -36,51 +36,51 @@
- any-glob-to-any-file:
- 'packages/lucide/*'
# For changes in the lucide React package
# For changes in the Lucide React package
⚛️ react package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react/*'
# For changes in the lucide React Native package
# For changes in the Lucide React Native package
⚛️ react native package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-react-native/*'
# For changes in the lucide vue packages
# For changes in the Lucide vue packages
💎 vue package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-vue/*'
- 'packages/lucide-vue-next/*'
# For changes in the lucide angular package
# For changes in the Lucide angular package
🅰️ angular package:
- changed-files:
- any-glob-to-any-file:
- 'packages/angular/*'
- 'packages/lucide-angular/*'
# For changes in the lucide preact package
# For changes in the Lucide preact package
⚛️ preact package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-preact/*'
# For changes in the lucide svelte package
# For changes in the Lucide svelte package
🧣 svelte package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-svelte/*'
# For changes in the lucide solid package
# For changes in the Lucide solid package
🪝 solid package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
# For changes in the lucide astro package
# For changes in the Lucide astro package
🚀 astro package:
- changed-files:
- any-glob-to-any-file:
@@ -92,13 +92,13 @@
- any-glob-to-any-file:
- 'packages/lucide-icons/*'
# For changes in the lucide static package
# For changes in the Lucide static package
🪨 static package:
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-static/*'
# For changes in the lucide flutter package
# For changes in the Lucide flutter package
🏹 flutter package:
- changed-files:
- any-glob-to-any-file:

2
.gitignore vendored
View File

@@ -50,4 +50,6 @@ docs/.vitepress/data/relatedIcons.json
docs/.vitepress/data/brandStopwords.json
docs/.vercel
docs/.nitro
docs/public/og/**
!docs/public/og/general.png
.gitignore

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,4 +1,6 @@
# Our Official Stance on Including Brand Logos in Lucide
# Brand Logos Statement
Our official stance on including brand logos in Lucide
## TL;DR

View File

@@ -1,74 +1,90 @@
# Contributor Covenant Code of Conduct
This Code of Conduct outlines our expectations for participant behavior as well as the consequences for unacceptable behavior within our community. We are committed to providing a welcoming, safe, and inclusive environment for all members of our community.
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
We pledge to make our community welcoming, safe, and equitable for all.
## Our Standards
We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
Examples of behavior that contributes to creating a positive environment
include:
## Encouraged Behaviors
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
Examples of unacceptable behavior by participants include:
With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
2. Engaging **kindly and honestly** with others.
3. Respecting **different viewpoints** and experiences.
4. **Taking responsibility** for our actions and contributions.
5. Gracefully giving and accepting **constructive feedback**.
6. Committing to **repairing harm** when it occurs.
7. Behaving in other ways that promote and sustain the **well-being of our community**.
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
## Restricted Behaviors
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
3. **Stereotyping or discrimination.** Characterizing anyones personality or behavior on the basis of immutable identities or traits.
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
7. Behaving in other ways that **threaten the well-being** of our community.
### Other Restrictions
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
## Reporting an Issue
Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
When an incident does occur, it is important to report it promptly. To report a possible violation, email: [info@lucide.dev](mailto:info@lucide.dev)
Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
## Addressing and Repairing Harm
If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
1) Warning
1) Event: A violation involving a single incident or series of incidents.
2) Consequence: A private, written warning from the Community Moderators.
3) Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
2) Temporarily Limited Activities
1) Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
2) Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
3) Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
3) Temporary Suspension
1) Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
2) Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
3) Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
4) Permanent Ban
1) Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
2) Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
3) Repair: There is no possible repair in cases of this severity.
This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at johnletey@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozillas code of conduct team](https://github.com/mozilla/inclusion).

View File

@@ -1,5 +1,4 @@
# Contribution Guidelines
# Contribution Guide
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
@@ -19,13 +18,15 @@ Guidelines for pull requests:
- __Make sure the target of your pull request is the relevant branch__. Most of bug fixes or new feature should go to the `main` branch.
- __Include only related work__. If your pull request has unrelated commits, it won't be accepted.
### Icon Pull Requests
### Contributing Icons
We love contributions of new icons from the community! If you want to contribute new icons, please follow the guidelines below.
#### Guidelines
Please make sure you follow the icon guidelines, that should be followed to keep quality and consistency when making icons for Lucide.
Read it here: [ICON_GUIDELINES](https://lucide.dev/docs/icon-design-guide).
Read it here: [ICON_GUIDELINES](https://lucide.dev/contribute/icon-design-guide).
#### Lucide Studio
@@ -35,15 +36,15 @@ For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) m
Here you can find instructions on how to implement the guidelines with different vector graphics editors:
##### [Adobe Illustrator Guide](https://lucide.dev/docs/illustrator-guide)
##### [Adobe Illustrator Guide](https://lucide.dev/contribute/illustrator-guide)
You can also [download an Adobe Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
You can also [download an Adobe Illustrator template](https://lucide.dev/templates/illustrator_template.ai).
##### [Inkscape Guide](https://lucide.dev/docs/inkscape-guide)
##### [Inkscape Guide](https://lucide.dev/contribute/inkscape-guide)
##### [Figma Guide](https://lucide.dev/docs/figma-guide)
##### [Figma Guide](https://lucide.dev/contribute/figma-guide)
##### [Affinity Designer Guide](https://lucide.dev/guide/design/affinity-designer-guide)
##### [Affinity Designer Guide](https://lucide.dev/contribute/affinity-designer-guide)
#### Submitting Multiple Icons
@@ -191,7 +192,7 @@ Detailed documentation about: installation, guides, packages, design guides etc.
### Icons
All the icons of lucide in SVG format. These will be used as source for all the packages and other distributions for the lucide icons.
All the icons of Lucide in SVG format. These will be used as source for all the packages and other distributions for the Lucide icons.
### Packages

View File

@@ -45,7 +45,7 @@ Lucide is an open-source icon library that provides 1000+ vector (svg) files for
### Figma
The lucide figma plugin.
The Lucide Figma plugin.
Visit [Figma community page](https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons) to install the plugin.

View File

@@ -1,7 +1,19 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress';
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons';
import sidebar from './sidebar';
import snackPlayer from './markdown/snackPlayer';
import sandpackPlugin from './markdown/sandpack';
import { readFile } from 'node:fs/promises';
import { resourcesSidebar } from './sidebar/resources';
import getStructuredData from './getStructuredData';
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
import llmstxt from 'vitepress-plugin-llms';
const defaultSandpackCSS = await readFile(
fileURLToPath(new URL('./theme/sandpack-default.css', import.meta.url)),
'utf-8',
);
const title = 'Lucide';
const socialTitle = 'Lucide Icons';
@@ -15,6 +27,20 @@ export default defineConfig({
cleanUrls: true,
outDir: '.vercel/output/static',
srcExclude: ['**/README.md'],
markdown: {
config(md) {
md.use(groupIconMdPlugin);
md.use(snackPlayer);
md.use(sandpackPlugin, {
defaultFiles: {
'/styles.css': {
code: defaultSandpackCSS,
hidden: true,
},
},
});
},
},
vite: {
resolve: {
alias: [
@@ -36,6 +62,19 @@ export default defineConfig({
},
],
},
plugins: [
groupIconVitePlugin(),
llmstxt({
ignoreFiles: [
'code-of-conduct.md',
'index.md',
'packages.md',
'showcase.md',
'brand-logo-statement.md',
'icons/**', // Not working, need investigation
],
}),
],
},
head: [
[
@@ -153,6 +192,7 @@ export default defineConfig({
],
],
async transformPageData(pageData) {
pageData.frontmatter.head ??= [];
if (
pageData.relativePath.startsWith('icons/') &&
!pageData.relativePath.startsWith('icons/lab/') &&
@@ -173,13 +213,58 @@ export default defineConfig({
const structuredData = await getStructuredData(iconName, pageData);
pageData.frontmatter.head ??= [];
const ogPath = await createIconOGImage(iconName, pageData.params?.tags || []);
if (ogPath) {
const content = `https://lucide.dev${ogPath}`;
pageData.frontmatter.head.push(
[
'meta',
{
property: 'og:image',
content,
},
],
[
'meta',
{
property: 'twitter:image',
content,
},
],
);
}
pageData.frontmatter.head.push([
'script',
{ type: 'application/ld+json' },
JSON.stringify(structuredData),
]);
}
if (pageData.relativePath.startsWith('guide/')) {
const ogPath = await createGeneralOGImage(pageData);
if (ogPath) {
const content = `https://lucide.dev${ogPath}`;
pageData.frontmatter.head.push(
[
'meta',
{
property: 'og:image',
content,
},
],
[
'meta',
{
property: 'twitter:image',
content,
},
],
);
}
}
},
themeConfig: {
logo: {
@@ -189,9 +274,15 @@ export default defineConfig({
nav: [
{ text: 'Icons', link: '/icons/' },
{ text: 'Guide', link: '/guide/' },
{
text: 'Resources',
items: [
...resourcesSidebar[0].items,
{ text: 'Design icons', link: '/contribute/icon-design-guide' },
],
},
{ text: 'Packages', link: '/packages' },
{ text: 'Showcase', link: '/showcase' },
{ text: 'License', link: '/license' },
],
sidebar,
socialLinks: [

View File

@@ -0,0 +1,79 @@
import { PageData } from 'vitepress';
import fs from 'node:fs/promises';
import { cwd } from 'node:process';
import { join } from 'node:path';
import sharp from 'sharp';
const generalSVGTemplate = fs.readFile(join(cwd(), '.vitepress/lib/guideOGTemplate.svg'), 'utf-8');
export async function createGeneralOGImage({ title, description, relativePath }: PageData) {
const fileName = relativePath.replace(/\.md$/, '.png').replace(/\//g, '-');
const path = join(cwd(), 'public/og', fileName);
let desc = description.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
if (!desc) {
desc = '';
}
const lines = desc.replace(/(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n').split('\n');
const data = {
title: title,
line1: lines[0] || '',
line2: lines[1] || '',
line3: lines[2] || '',
line4: lines[3] || '',
};
const svgTemplate = await generalSVGTemplate;
const svg = svgTemplate.replace(/\{\{([^}]+)\}\}/g, (_, name: keyof typeof data) => data[name]);
console.log(`Generating ${fileName}`);
try {
await sharp(Buffer.from(svg))
.resize(1200 * 1.1, 630 * 1.1)
.png()
.toFile(path);
} catch (e) {
console.error('Error generating', { filename: path, ...data, svg });
console.error(e);
}
return `/og/${fileName}`;
}
const iconSVGTemplate = fs.readFile(join(cwd(), '.vitepress/lib/iconOGTemplate.svg'), 'utf-8');
export async function createIconOGImage(iconName: string, tags: string[]) {
const path = join(cwd(), 'public/og', `icon-${iconName}.png`);
let iconSVG = await fs.readFile(join(cwd(), `../icons/${iconName}.svg`), 'utf-8');
iconSVG = iconSVG
.replace('width="24"', 'width="320"')
.replace('height="24"', 'height="320"')
.replace('currentColor', '#dfdfd6')
.replace(
'xmlns="http://www.w3.org/2000/svg"',
'xmlns="http://www.w3.org/2000/svg" x="62" y="66"',
);
const data = {
name: iconName,
tags: tags.join(' • ').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/&/g, '&amp;'),
iconSVG,
};
const svgTemplate = await iconSVGTemplate;
const svg = svgTemplate.replace(/\{\{([^}]+)\}\}/g, (_, name: keyof typeof data) => data[name]);
console.log(`Generating ${path}`);
try {
await sharp(Buffer.from(svg))
.resize(1200 * 1.1, 630 * 1.1)
.png()
.toFile(path);
} catch (e) {
console.error('Error generating', { filename: path, ...data, svg });
console.error(e);
}
return `/og/icon-${iconName}.png`;
}

View File

@@ -18,6 +18,7 @@
"lucide-react": {
"order": 1,
"icon": "react",
"docsAlias": "react",
"shields": [
{
"alt": "npm",
@@ -34,14 +35,25 @@
"@lucide/vue": {
"order": 2,
"icon": "vue",
"docsAlias": "lucide-vue",
"docsAlias": "vue",
"packageDirname": "vue",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/@lucide/vue",
"href": "https://www.npmjs.com/package/@lucide/vue"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/@lucide/vue",
"href": "https://www.npmjs.com/package/@lucide/vue"
}
]
},
"lucide-svelte": {
"order": 3,
"icon": "svelte",
"docsAlias": "svelte",
"shields": [
{
"alt": "npm",
@@ -58,6 +70,7 @@
"lucide-solid": {
"order": 4,
"icon": "solid",
"docsAlias": "solid",
"shields": [
{
"alt": "npm",
@@ -74,6 +87,7 @@
"lucide-react-native": {
"order": 5,
"icon": "react-native",
"docsAlias": "react-native",
"shields": [
{
"alt": "npm",
@@ -90,6 +104,7 @@
"@lucide/angular": {
"order": 6,
"icon": "angular",
"docsAlias": "angular",
"shields": [
{
"alt": "npm",
@@ -105,7 +120,9 @@
},
"lucide-angular": {
"order": 7,
"icon": "angular",
"icon": "angular-old",
"description": "Our legacy Angular package with a module based API.",
"documentation": "https://v0.lucide.dev/guide/packages/lucide-angular",
"shields": [
{
"alt": "npm",
@@ -122,6 +139,7 @@
"lucide-preact": {
"order": 8,
"icon": "preact",
"docsAlias": "preact",
"shields": [
{
"alt": "npm",
@@ -136,7 +154,7 @@
]
},
"@lucide/astro": {
"docsAlias": "lucide-astro",
"docsAlias": "astro",
"packageDirname": "astro",
"order": 9,
"icon": "astro",
@@ -157,6 +175,7 @@
"lucide-static": {
"order": 10,
"icon": "svg",
"docsAlias": "static",
"shields": [
{
"alt": "npm",
@@ -171,6 +190,7 @@
]
},
"@lucide/icons": {
"hide": true,
"docsAlias": "icons",
"packageDirname": "icons",
"order": 10,

View File

@@ -117,7 +117,7 @@
},
{
"name": "lucide-slint",
"description": "Implementation of the lucide icon library for Slint.",
"description": "Implementation of the Lucide icon library for Slint.",
"icon": "/framework-logos/slint.svg",
"shields": [
{

View File

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

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630" fill="none">
<g clip-path="url(#a)">
<path fill="#1b1b1f" d="M0 0h1200v630H0z" />
<text xml:space="preserve" fill="#fff" font-family="Inter, Helvetica, sans-serif" font-size="44" font-weight="600"
letter-spacing=".05em" style="white-space:pre">
<tspan x="62" y="118.5">{{title}}</tspan>
</text>
<text xml:space="preserve" fill="#ebebf5" fill-opacity=".8" font-family="Inter, Helvetica, sans-serif"
font-size="32" letter-spacing="0em" opacity=".79" style="white-space:pre">
<tspan x="62" y="209.14">{{line1}}</tspan>
<tspan x="62" y="257.14">{{line2}}</tspan>
<tspan x="62" y="305.14">{{line3}}</tspan>
<tspan x="62" y="353.14">{{line4}}</tspan>
</text>
<path fill="#161618" d="M0 438h1200v192H0z" />
<path fill="#e7e7e7"
d="M395.33 562.28q-5.97 0-10.31-2.5a17 17 0 0 1-6.64-7.13q-2.32-4.64-2.32-10.91 0-6.18 2.32-10.84a17.7 17.7 0 0 1 6.56-7.29q4.2-2.63 9.9-2.63 3.66 0 6.92 1.2 3.28 1.17 5.78 3.64a17 17 0 0 1 3.98 6.3 26 26 0 0 1 1.45 9.07v2.89h-32.52v-6.36H404q-.03-2.7-1.17-4.81a8.5 8.5 0 0 0-3.17-3.36 9 9 0 0 0-4.7-1.23q-2.88 0-5.04 1.4a9.7 9.7 0 0 0-3.39 3.65q-1.19 2.25-1.21 4.93v5.54q0 3.5 1.27 6a9 9 0 0 0 3.53 3.8q2.29 1.3 5.32 1.3 2.05 0 3.7-.58a8 8 0 0 0 2.87-1.74 7 7 0 0 0 1.83-2.84l8.73 1a14 14 0 0 1-3.15 6.09 15.4 15.4 0 0 1-5.89 4q-3.6 1.41-8.21 1.41M346.67 562.2q-4.67 0-8.37-2.42a16.4 16.4 0 0 1-5.84-7.03q-2.14-4.6-2.14-11.2 0-6.67 2.17-11.25a16 16 0 0 1 5.91-6.95 15 15 0 0 1 8.3-2.37q3.48 0 5.73 1.2 2.25 1.17 3.56 2.84 1.32 1.63 2.05 3.1h.38v-19.95h9.38v53.33h-9.2v-6.3h-.56q-.74 1.45-2.1 3.1-1.37 1.61-3.61 2.76a12.4 12.4 0 0 1-5.66 1.14m2.6-7.73q2.99 0 5.07-1.62a10 10 0 0 0 3.18-4.55 19 19 0 0 0 1.08-6.8 19 19 0 0 0-1.08-6.74 9.6 9.6 0 0 0-3.15-4.46 8 8 0 0 0-5.1-1.59 8.2 8.2 0 0 0-5.2 1.64 10 10 0 0 0-3.16 4.54 19 19 0 0 0-1.06 6.61q0 3.75 1.06 6.7a10.5 10.5 0 0 0 3.18 4.6 8 8 0 0 0 5.19 1.67M312.98 561.5v-40h9.35v40zm4.7-45.68q-2.22 0-3.82-1.48a4.8 4.8 0 0 1-1.6-3.62q0-2.13 1.6-3.62a5.4 5.4 0 0 1 3.82-1.51q2.25 0 3.82 1.51 1.6 1.49 1.6 3.62a4.8 4.8 0 0 1-1.6 3.62 5.4 5.4 0 0 1-3.82 1.48M288.3 562.28q-5.93 0-10.2-2.63-4.23-2.62-6.53-7.26a24 24 0 0 1-2.27-10.73q0-6.1 2.32-10.76a17.4 17.4 0 0 1 6.56-7.29 19 19 0 0 1 10.07-2.63q4.83 0 8.56 1.8a14.6 14.6 0 0 1 5.96 5.02 15 15 0 0 1 2.53 7.55h-8.93a8.6 8.6 0 0 0-2.59-4.81q-2.01-1.95-5.4-1.96a8.4 8.4 0 0 0-5.03 1.57 10 10 0 0 0-3.39 4.42 18 18 0 0 0-1.19 6.93q0 4.1 1.2 7.03 1.19 2.93 3.33 4.5a8.5 8.5 0 0 0 5.08 1.57q2.07 0 3.7-.78a7.5 7.5 0 0 0 2.76-2.32q1.11-1.5 1.53-3.67h8.93a16 16 0 0 1-2.48 7.52 14.7 14.7 0 0 1-5.83 5.1 19.4 19.4 0 0 1-8.68 1.83M252.04 544.68V521.5h9.34v40h-9.06v-7.11h-.41a12 12 0 0 1-4.42 5.5q-3.05 2.13-7.52 2.13-3.9 0-6.9-1.74a12 12 0 0 1-4.64-5.13 18 18 0 0 1-1.68-8.18V521.5h9.35v24.01q0 3.8 2.06 6.04a7 7 0 0 0 5.43 2.24q2.07 0 4-1.01a8 8 0 0 0 3.18-3.02 9.4 9.4 0 0 0 1.26-5.08M185.96 561.5v-53.33h9.58v45.23h23.3v8.1zM117.1 534c0-7.6-6.1-13.75-13.64-13.75A13.7 13.7 0 0 0 89.82 534c0 17.72 14.25 32.08 31.83 32.08s31.82-14.36 31.82-32.08a50.5 50.5 0 0 0-16.67-37.58 4.6 4.6 0 0 1-.36-6.47 4.5 4.5 0 0 1 6.42-.36 59.7 59.7 0 0 1 19.7 44.4l-.05 2.13c-1.1 21.8-18.97 39.13-40.86 39.13-22.6 0-40.92-18.47-40.92-41.25a22.8 22.8 0 0 1 22.73-22.92A22.8 22.8 0 0 1 126.2 534a4.56 4.56 0 0 1-4.55 4.58 4.56 4.56 0 0 1-4.54-4.58" />
<path fill="#f56565"
d="M98.91 534a4.56 4.56 0 0 1 4.55-4.58 4.57 4.57 0 0 1 4.55 4.58c0 7.6 6.1 13.75 13.63 13.75 7.3 0 13.26-5.78 13.62-13.04l.02-.71c0-17.72-14.24-32.08-31.82-32.08S71.64 516.28 71.64 534a50.5 50.5 0 0 0 16.93 37.81 4.6 4.6 0 0 1 .4 6.48 4.53 4.53 0 0 1-6.42.4 59.7 59.7 0 0 1-19.98-43.03l-.02-1.66c0-22.78 18.31-41.25 40.91-41.25s40.92 18.47 40.92 41.25l-.04 1.18a22.8 22.8 0 0 1-22.7 21.74A22.8 22.8 0 0 1 98.92 534" />
<text xml:space="preserve" fill="#ebebf5" fill-opacity=".8" font-family="Inter, Helvetica, sans-serif"
font-size="39" font-weight="500" letter-spacing="0em" style="white-space:pre">
<tspan x="808.98" y="548.18">https://lucide.dev</tspan>
</text>
<path fill="url(#b)" d="M1116 0h84v435h-84z" />
</g>
<defs>
<linearGradient id="b" x1="1200" x2="1116" y1="218" y2="218" gradientUnits="userSpaceOnUse">
<stop stop-color="#1b1b1f" />
<stop offset="1" stop-color="#1b1b1f" stop-opacity="0" />
</linearGradient>
<clipPath id="a">
<path fill="#fff" d="M0 0h1200v630H0z" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,47 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" fill="none">
<g clip-path="url(#a)">
<path fill="#1b1b1f" d="M0 0h1200v630H0z" />
<g clip-path="url(#b)">
<rect width="320" height="320" x="62" y="66" fill="#161618" rx="24" />
<path stroke="#202127"
d="M62 79.33h320M75.33 66v320M62 92.67h320M88.67 66v320M62 106h320M102 66v320M62 119.33h320M115.33 66v320M62 132.67h320M128.67 66v320M62 146h320M142 66v320M62 159.33h320M155.33 66v320M62 172.67h320M168.67 66v320M62 186h320M182 66v320M62 199.33h320M195.33 66v320M62 212.67h320M208.67 66v320M62 226h320M222 66v320M62 239.33h320M235.33 66v320M62 252.67h320M248.67 66v320M62 266h320M262 66v320M62 279.33h320M275.33 66v320M62 292.67h320M288.67 66v320M62 306h320M302 66v320M62 319.33h320M315.33 66v320M62 332.67h320M328.67 66v320M62 346h320M342 66v320M62 359.33h320M355.33 66v320M62 372.67h320M368.67 66v320" />
{{iconSVG}}
</g>
<text xml:space="preserve" fill="#ebebf5" fill-opacity=".8" font-family="Inter" font-size="32" letter-spacing="0em"
opacity=".79" style="white-space:pre">
<tspan x="434" y="293.64">See the icon preview, code examples,
</tspan>
<tspan x="434" y="341.64">release details and other related icons.</tspan>
</text>
<text xml:space="preserve" fill="#fff" font-family="Inter" font-size="44" font-weight="600" letter-spacing=".05em"
style="white-space:pre">
<tspan x="438" y="130.5">{{name}}</tspan>
</text>
<text xml:space="preserve" fill="#98989f" font-family="Inter" font-size="32" letter-spacing=".05em"
style="white-space:pre">
<tspan x="438" y="208.14">{{tags}}</tspan>
</text>
<path fill="#161618" d="M0 438h1200v192H0z" />
<path fill="#e7e7e7"
d="M395.33 562.28q-5.97 0-10.31-2.5a17 17 0 0 1-6.64-7.13q-2.32-4.64-2.32-10.92 0-6.17 2.32-10.83a17.7 17.7 0 0 1 6.56-7.3q4.2-2.62 9.9-2.62 3.66 0 6.92 1.2 3.28 1.17 5.78 3.64a17 17 0 0 1 3.98 6.3 26 26 0 0 1 1.45 9.07v2.89h-32.52v-6.36H404q-.03-2.7-1.17-4.81a8.5 8.5 0 0 0-3.17-3.36 9 9 0 0 0-4.7-1.23q-2.88 0-5.04 1.4a9.7 9.7 0 0 0-3.39 3.65q-1.19 2.25-1.21 4.93v5.54q0 3.5 1.27 6a9 9 0 0 0 3.53 3.8q2.29 1.3 5.32 1.3 2.05 0 3.7-.58a8 8 0 0 0 2.87-1.74 7 7 0 0 0 1.83-2.84l8.73 1a14 14 0 0 1-3.15 6.09 15.4 15.4 0 0 1-5.89 4q-3.6 1.41-8.21 1.41M346.67 562.2q-4.67 0-8.37-2.42a16.4 16.4 0 0 1-5.84-7.03q-2.14-4.6-2.14-11.2 0-6.67 2.17-11.25a16 16 0 0 1 5.91-6.95 15 15 0 0 1 8.3-2.37q3.48 0 5.73 1.2 2.25 1.17 3.56 2.84 1.32 1.63 2.05 3.1h.38v-19.95h9.38v53.33h-9.2v-6.3h-.56q-.74 1.45-2.1 3.1-1.37 1.61-3.61 2.76a12.4 12.4 0 0 1-5.66 1.14m2.6-7.73q2.99 0 5.07-1.62a10 10 0 0 0 3.18-4.55 19 19 0 0 0 1.08-6.8 19 19 0 0 0-1.08-6.74 9.6 9.6 0 0 0-3.15-4.46 8 8 0 0 0-5.1-1.59 8.2 8.2 0 0 0-5.2 1.64 10 10 0 0 0-3.16 4.54 19 19 0 0 0-1.06 6.61q0 3.75 1.06 6.7a10.5 10.5 0 0 0 3.18 4.6 8 8 0 0 0 5.19 1.67M312.98 561.5v-40h9.35v40zm4.7-45.68q-2.22 0-3.82-1.48a4.8 4.8 0 0 1-1.6-3.62q0-2.13 1.6-3.62a5.4 5.4 0 0 1 3.82-1.51q2.25 0 3.82 1.51 1.6 1.49 1.6 3.62a4.8 4.8 0 0 1-1.6 3.62 5.4 5.4 0 0 1-3.82 1.48M288.3 562.28q-5.93 0-10.2-2.63-4.23-2.62-6.53-7.26a24 24 0 0 1-2.27-10.73q0-6.1 2.32-10.76a17.4 17.4 0 0 1 6.56-7.29 19 19 0 0 1 10.07-2.63q4.83 0 8.56 1.8a14.6 14.6 0 0 1 5.96 5.02 15 15 0 0 1 2.53 7.55h-8.93a8.6 8.6 0 0 0-2.59-4.81q-2.01-1.95-5.4-1.96a8.4 8.4 0 0 0-5.03 1.57 10 10 0 0 0-3.39 4.42 18 18 0 0 0-1.19 6.93q0 4.1 1.2 7.03 1.19 2.93 3.33 4.5a8.5 8.5 0 0 0 5.08 1.57q2.07 0 3.7-.78a7.5 7.5 0 0 0 2.76-2.32q1.11-1.5 1.53-3.67h8.93a16 16 0 0 1-2.48 7.52 14.7 14.7 0 0 1-5.83 5.1 19.4 19.4 0 0 1-8.68 1.83M252.04 544.68V521.5h9.34v40h-9.06v-7.11h-.41a12 12 0 0 1-4.42 5.5q-3.05 2.13-7.52 2.13-3.9 0-6.9-1.74a12 12 0 0 1-4.64-5.13 18 18 0 0 1-1.68-8.18V521.5h9.35v24.01q0 3.8 2.06 6.04a7 7 0 0 0 5.43 2.24q2.07 0 4-1.01a8 8 0 0 0 3.18-3.02 9.4 9.4 0 0 0 1.26-5.08M185.96 561.5v-53.33h9.58v45.23h23.3v8.1zM117.1 534c0-7.6-6.1-13.75-13.64-13.75A13.7 13.7 0 0 0 89.82 534c0 17.72 14.25 32.08 31.83 32.08s31.82-14.36 31.82-32.08a50.5 50.5 0 0 0-16.67-37.58 4.6 4.6 0 0 1-.36-6.47 4.5 4.5 0 0 1 6.42-.36 59.7 59.7 0 0 1 19.7 44.4l-.05 2.13c-1.1 21.8-18.97 39.13-40.86 39.13-22.6 0-40.92-18.47-40.92-41.25a22.8 22.8 0 0 1 22.73-22.92A22.8 22.8 0 0 1 126.2 534a4.56 4.56 0 0 1-4.55 4.58 4.56 4.56 0 0 1-4.54-4.58" />
<path fill="#f56565"
d="M98.91 534a4.56 4.56 0 0 1 4.55-4.58 4.57 4.57 0 0 1 4.55 4.58c0 7.6 6.1 13.75 13.63 13.75 7.3 0 13.26-5.78 13.62-13.04l.02-.71c0-17.72-14.24-32.08-31.82-32.08S71.64 516.28 71.64 534a50.5 50.5 0 0 0 16.93 37.81 4.6 4.6 0 0 1 .4 6.48 4.53 4.53 0 0 1-6.42.4 59.7 59.7 0 0 1-19.98-43.03l-.02-1.66c0-22.78 18.31-41.25 40.91-41.25s40.92 18.47 40.92 41.25l-.04 1.18a22.8 22.8 0 0 1-22.7 21.74A22.8 22.8 0 0 1 98.92 534" />
<text xml:space="preserve" fill="#ebebf5" fill-opacity=".8" font-family="Inter" font-size="39" font-weight="500"
letter-spacing="0em" style="white-space:pre">
<tspan x="808.98" y="548.18">https://lucide.dev</tspan>
</text>
<path fill="url(#c)" d="M1116 0h84v435h-84z" />
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h1200v630H0z" />
</clipPath>
<clipPath id="b">
<rect width="320" height="320" x="62" y="66" fill="#fff" rx="24" />
</clipPath>
<linearGradient id="c" x1="1200" x2="1116" y1="218" y2="218" gradientUnits="userSpaceOnUse">
<stop stop-color="#1b1b1f" />
<stop offset="1" stop-color="#1b1b1f" stop-opacity="0" />
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,138 @@
import type MarkdownIt from 'markdown-it';
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
import container from 'markdown-it-container';
import sandpackTheme from '../theme/sandpackTheme.json';
type SnackParams = {
defaultFiles?: Record<
string,
{
code: string;
active?: boolean;
hidden?: boolean;
}
>;
};
type ContainerArgs = [typeof container, string, { render: RenderRule }];
export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParams = {}) {
if (md == null) {
throw new Error('MarkdownIt instance is required for sandpackPlugin');
}
const escapeHtml = md?.utils?.escapeHtml;
const defaultFence =
md.renderer.rules.fence ||
((tokens, idx, options, env, self) => self.renderToken(tokens, idx, options));
const renderSandbox = (tokenList: any[], index: number) => {
const renderFunc = (tokens: any[], idx: number) => {
if (tokens[idx].nesting === 1) {
const fileAttr: string[] = [];
const attrs = Object.fromEntries(tokens[idx].attrs || []);
const files: Record<
string,
{
code: string;
active?: boolean;
hidden?: boolean;
}
> = {};
for (
let i = idx + 1;
!(tokens[i].nesting === -1 && tokens[i].type === 'container_sandpack_close');
++i
) {
if (tokens[i].type === 'fence' && tokens[i].tag === 'code') {
const info = tokens[i].info ?? '';
const [lang, fileName, params = ''] = info.split(' ');
const active = params.includes('[active]');
const hidden = params.includes('[hidden]');
const code = tokens[i].content;
if (fileName && code) {
files[fileName] = {
code,
};
if (active) {
(files[fileName] as any).active = true;
}
if (hidden) {
(files[fileName] as any).hidden = true;
}
}
}
}
const { dependencies, showTabs, externalResources, editorWidthPercentage, ...options } =
attrs;
const dependencyList = dependencies?.split(',')?.map((dep: string) => dep.trim()) ?? [];
const dependencyObject = dependencyList.reduce(
(acc: Record<string, string>, name: string) => {
acc[name] = 'latest';
return acc;
},
{},
);
const externalResourcesList = externalResources
?.split(',')
?.map((res: string) => res.trim())
?.filter((res: string) => res.length > 0);
const filesWithDefaultStyles = {
...pluginOptions.defaultFiles,
...files,
};
return `\
<Sandpack\
template="${escapeHtml(attrs.template || 'vanilla')}"\
:theme="${escapeHtml(JSON.stringify(sandpackTheme))}"\
:customSetup="${escapeHtml(
dependencyList
? JSON.stringify({
dependencies: dependencyList.length ? dependencyObject : {},
})
: undefined,
)}"
:files="${escapeHtml(JSON.stringify(filesWithDefaultStyles))}"\
:options="${escapeHtml(
JSON.stringify({
...(showTabs ? { showTabs: JSON.parse(showTabs) } : {}),
externalResources: externalResourcesList,
editorWidthPercentage: editorWidthPercentage
? Number(editorWidthPercentage)
: undefined,
...options,
}),
)}"\
>`;
}
return `</Sandpack>`;
};
return renderFunc(tokenList, index);
};
function createCodeGroup(md: MarkdownIt): ContainerArgs {
return [
container,
'sandpack',
{
render(tokens, idx) {
return renderSandbox(tokens, idx);
},
},
];
}
md.use(...createCodeGroup(md));
}

View File

@@ -0,0 +1,99 @@
/**
* 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,143 +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: 'React',
link: '/guide/packages/lucide-react',
},
{
text: 'Vue',
link: '/guide/packages/lucide-vue',
},
{
text: 'Svelte',
link: '/guide/packages/lucide-svelte',
},
{
text: 'Solid',
link: '/guide/packages/lucide-solid',
},
{
text: 'React Native',
link: '/guide/packages/lucide-react-native',
},
{
text: 'Angular',
link: '/guide/packages/angular',
},
{
text: 'Preact',
link: '/guide/packages/lucide-preact',
},
{
text: 'Astro',
link: '/guide/packages/lucide-astro',
},
{
text: 'Static',
link: '/guide/packages/lucide-static',
},
{
text: 'Icon data & helpers',
link: '/guide/packages/icons',
},
],
},
{
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,107 @@
import { DefaultTheme } from 'vitepress';
export const angularSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/angular/',
},
{
text: 'Getting started',
link: '/guide/angular/getting-started',
desc: 'Learn how to get started with Lucide for Angular.',
},
{
text: 'Migration from v0',
link: '/guide/angular/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/angular/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/angular/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/angular/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/angular/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/angular/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/angular/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With Lucide Lab',
link: '/guide/angular/advanced/with-lucide-lab',
desc: 'Using lucide-lab with @lucide/angular',
},
// {
// text: 'Animations',
// link: '/guide/angular/advanced/animations',
// desc: 'Add animations to your icons',
// },
{
text: 'Filled icons',
link: '/guide/angular/advanced/filled-icons',
desc: 'Using filled icons in @lucide/angular',
},
{
text: 'Combining icons',
link: '/guide/angular/advanced/combining-icons',
desc: 'Combine multiple icons into one',
},
// {
// text: 'Dynamic icon component',
// link: '/guide/angular/advanced/dynamic-icon-component.md',
// desc: 'Dynamically import icons as needed',
// },
{
text: 'Icon provider',
link: '/guide/angular/advanced/icon-provider',
desc: 'Provide icons at app level and use them by name',
},
],
},
{
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 astroSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/astro/',
},
{
text: 'Getting started',
link: '/guide/astro/getting-started',
desc: 'Learn how to get started with Lucide for Astro.',
},
{
text: 'Migration from v0',
link: '/guide/astro/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide Astro.',
},
],
},
{
text: 'Basics',
items: [
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/astro/basics/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/astro/basics/sizing',
},
{
text: 'Stroke width',
desc: 'Adjust the stroke width of your icons',
link: '/guide/astro/basics/stroke-width',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Typescript',
link: '/guide/astro/advanced/typescript',
desc: 'All exported types and how to use them',
},
{
text: 'Accessibility',
link: '/guide/astro/advanced/accessibility',
desc: 'Making your icons accessible',
},
{
text: 'Global styling',
link: '/guide/astro/advanced/global-styling',
desc: 'Apply global styles to all icons',
},
{
text: 'With Lucide Lab',
link: '/guide/astro/advanced/with-lucide-lab',
desc: 'Using lucide-lab with @lucide/astro',
},
],
},
{
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,49 @@
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';
import { astroSidebar } from './astro';
import { resourcesSidebar } from './resources';
import { angularSidebar } from './angular';
import { lucideStaticSidebar } from './static';
type Sidebar = UserConfig<DefaultTheme.Config>['themeConfig']['sidebar'];
export const guideSidebarTop: DefaultTheme.SidebarItem[] = [
{
text: 'Introduction',
items: [
{ text: 'What is Lucide?', link: '/guide/' },
{ text: 'Version 1', link: '/guide/version-1' },
{ 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/astro/': astroSidebar,
'/guide/preact/': preactSidebar,
'/guide/react-native/': reactNativeSidebar,
'/guide/angular/': angularSidebar,
'/guide/static/': lucideStaticSidebar,
'/community': resourcesSidebar,
'/contribute': resourcesSidebar,
'/license': resourcesSidebar,
'/code-of-conduct': resourcesSidebar,
'/brand-logo-statement': resourcesSidebar,
// This should be here to keep the sidebar shown on the icons page
icons: [{ text: '', link: '/' }],
};
export default sidebar;

View File

@@ -0,0 +1,87 @@
import { DefaultTheme } from 'vitepress';
export const lucideSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/lucide/',
},
{
text: 'Getting started',
link: '/guide/lucide/getting-started',
desc: 'Learn how to get started with Lucide.',
},
{
text: 'Migration from v0',
link: '/guide/lucide/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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',
},
],
},
{
text: 'Advanced',
items: [
{
text: 'Global styling',
link: '/guide/lucide/advanced/global-styling',
desc: 'Apply options and styles globally',
},
{
text: 'Shadow DOM',
link: '/guide/lucide/advanced/shadow-dom',
desc: 'All exported types and how to use them',
},
{
text: 'Template element',
link: '/guide/lucide/advanced/content-template-element',
desc: 'Using content template element with lucide',
},
{
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',
},
],
},
{
text: 'Resources',
items: [
{
text: 'Accessibility in depth',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
],
},
] satisfies DefaultTheme.SidebarItem[] & { items: { desc?: string }[] }[];

View File

@@ -0,0 +1,102 @@
import { DefaultTheme } from 'vitepress';
export const preactSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/preact/',
},
{
text: 'Getting started',
link: '/guide/preact/getting-started',
desc: 'Learn how to get started with Lucide for PReact.',
},
{
text: 'Migration from v0',
link: '/guide/preact/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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,87 @@
import { DefaultTheme } from 'vitepress';
export const reactNativeSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/react-native/',
},
{
text: 'Getting started',
link: '/guide/react-native/getting-started',
desc: 'Learn how to get started with Lucide React Native',
},
{
text: 'Migration from v0',
link: '/guide/react-native/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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,113 @@
import { DefaultTheme } from 'vitepress';
export const reactSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/react/',
},
{
text: 'Getting started',
link: '/guide/react/getting-started',
desc: 'Learn how to get started with Lucide for React.',
},
{
text: 'Migration from v0',
link: '/guide/react/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
{
text: 'Migration from React Feather',
link: '/guide/react/migration-from-feather',
desc: 'Learn how to migrate from `react-feather` to `lucide-react`',
},
],
},
{
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,52 @@
export const resourcesSidebar = [
{
text: 'About',
items: [
{
text: 'License',
link: '/license',
},
{
text: 'Community',
link: '/community',
},
{
text: 'Code of Conduct',
link: '/code-of-conduct',
},
{
text: 'Brand logo statement',
link: '/brand-logo-statement',
},
{
text: 'Contributing',
link: '/contribute',
},
],
},
{
text: 'Designing icons',
items: [
{
text: 'Icon Design Principles',
link: '/contribute/icon-design-guide',
},
{
text: 'Designing in Illustrator',
link: '/contribute/illustrator-guide',
},
{
text: 'Designing in Inkscape',
link: '/contribute/inkscape-guide',
},
{
text: 'Designing in Figma',
link: '/contribute/figma-guide',
},
{
text: 'Designing in Affinity Designer',
link: '/contribute/affinity-designer-guide',
},
],
},
];

View File

@@ -0,0 +1,103 @@
import { DefaultTheme } from 'vitepress';
export const solidSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/solid/',
},
{
text: 'Getting started',
link: '/guide/solid/getting-started',
desc: 'Learn how to get started with Lucide.',
},
{
text: 'Migration from v0',
link: '/guide/solid/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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',
},
// Add support for this later
// {
// 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,88 @@
import { DefaultTheme } from 'vitepress';
export const lucideStaticSidebar = [
{
items: [
{
text: 'Overview',
desc: 'Overview of using Lucide icons as static assets in your projects',
link: '/guide/static/',
},
{
text: 'Getting started',
link: '/guide/static/getting-started',
desc: 'Learn how to get started with Lucide static.',
},
{
text: 'Migration from v0',
link: '/guide/static/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide static.',
},
],
},
{
text: 'SVG Files & Sprite',
items: [
{
text: 'Import SVG files as images',
desc: 'Use icons as images in your project',
link: '/guide/static/link-as-image',
},
{
text: 'SVG Sprite',
desc: 'Use SVG sprites in your project',
link: '/guide/static/svg-sprite',
},
],
},
{
text: 'Icon Font',
items: [
{
text: 'Import as font',
desc: 'Use icons as a web font in your project',
link: '/guide/static/font/',
},
{
text: 'Color',
desc: 'Adjust the color of your icons',
link: '/guide/static/font/color',
},
{
text: 'Sizing',
desc: 'Adjust the size of your icons',
link: '/guide/static/font/sizing',
},
],
},
{
text: 'SVG String JS modules',
items: [
{
text: 'Use in Node.js',
desc: 'Use Lucide in your Node.js projects',
link: '/guide/static/js-modules/node',
},
{
text: 'Use in JS projects',
desc: 'Use Lucide in your JavaScript projects',
link: '/guide/static/js-modules/web',
},
],
},
{
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 svelteSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/svelte/',
},
{
text: 'Getting started',
link: '/guide/svelte/getting-started',
desc: 'Learn how to get started with Lucide for Svelte.',
},
{
text: 'Migration from v0',
link: '/guide/svelte/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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,98 @@
import { DefaultTheme } from 'vitepress';
export const vueSidebar = [
{
items: [
{
text: 'Overview',
link: '/guide/vue/',
},
{
text: 'Getting started',
link: '/guide/vue/getting-started',
desc: 'Learn how to get started with Lucide for Vue.',
},
{
text: 'Migration from v0',
link: '/guide/vue/migration',
desc: 'Learn how to migrate from v0 to v1 of Lucide.',
},
],
},
{
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

@@ -1,62 +1,58 @@
<script setup lang="ts">
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue'
import { computed, ref } from 'vue'
import {
Listbox,
ListboxButton,
ListboxOptions,
ListboxOption,
} from '@headlessui/vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import { chevronUp } from '../../../data/iconNodes'
import { useStorage } from '@vueuse/core'
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import { computed, ref } from 'vue';
import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from '@headlessui/vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { chevronUp } from '../../../data/iconNodes';
import { useStorage } from '@vueuse/core';
interface Props {
options: {
text: string
onClick?: () => void
}[],
callOptionOnClick?: boolean
buttonClass?: string
id: string
popoverPosition?: 'top' | 'bottom'
text: string;
onClick?: () => void;
}[];
callOptionOnClick?: boolean;
buttonClass?: string;
id: string;
popoverPosition?: 'top' | 'bottom';
}
const props = withDefaults(defineProps<Props>(), {
callOptionOnClick: false,
popoverPosition: 'bottom'
})
popoverPosition: 'bottom',
});
const emit = defineEmits(['click', 'optionClick'])
const emit = defineEmits(['click', 'optionClick']);
const buttonRef = ref(null)
const buttonRef = ref(null);
const selectedOption = useStorage(props.id, props.options[0].text)
const selectionOptionAction = computed(() => props.options.find(option => option.text === selectedOption.value).onClick)
const selectedOption = useStorage(props.id, props.options[0].text);
const selectionOptionAction = computed(
() => props.options.find((option) => option.text === selectedOption.value).onClick,
);
function onClick(event) {
selectionOptionAction.value()
selectionOptionAction.value();
emit('click', event)
emit('click', event);
}
function onOptionClick(event, option) {
if(!props.callOptionOnClick) {
return
if (!props.callOptionOnClick) {
return;
}
option.onClick()
option.onClick();
emit('optionClick', event)
emit('optionClick', event);
}
const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
const ChevronUp = createLucideIcon('ChevronUp', chevronUp);
</script>
<template>
<Listbox v-model="selectedOption">
<div class="menu" >
<div class="menu">
<div class="button-wrapper">
<VPButton
v-bind="$attrs"
@@ -75,7 +71,10 @@ const ChevronUp = createLucideIcon('ChevronUp', chevronUp)
:class="popoverPosition"
/>
</div>
<ListboxOptions class="menu-items" :class="popoverPosition">
<ListboxOptions
class="menu-items"
:class="popoverPosition"
>
<ListboxOption
as="button"
class="menu-item"
@@ -122,7 +121,9 @@ ul.menu-items {
font-weight: 500;
color: var(--vp-c-text-1);
white-space: nowrap;
transition: background-color .25s,color .25s;
transition:
background-color 0.25s,
color 0.25s;
list-style: none;
}

View File

@@ -1,5 +1,5 @@
<script setup>
import Icon from 'lucide-vue-next/src/Icon';
import Icon from '@lucide/vue/src/Icon';
import { search } from '../../../data/iconNodes';
defineProps({

View File

@@ -12,7 +12,7 @@ export interface InputProps {
<script setup lang="ts">
import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from 'lucide-vue-next/src/Icon';
import Icon from '@lucide/vue/src/Icon';
import { x } from '../../../data/iconNodes';
import IconButton from './IconButton.vue';
@@ -132,7 +132,7 @@ defineExpose({
right: 68px;
top: 9px;
padding: 4px;
transition: background-color .25s;
transition: background-color 0.25s;
}
.shortcut {

View File

@@ -7,7 +7,7 @@ export default {
<script setup lang="ts">
import { computed, ref } from 'vue';
import Input from './Input.vue';
import Icon from 'lucide-vue-next/src/Icon';
import Icon from '@lucide/vue/src/Icon';
import { search } from '../../../data/iconNodes';
interface Props {

View File

@@ -0,0 +1,67 @@
<script setup lang="ts">
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
</script>
<template>
<div class="top-notification">
<p>
Release candidate for
<VPLink
class="notification-link"
href="/guide/version-1"
>Lucide v1 is out!</VPLink
>🚀<br />
You're looking at the site for v1, for v0 go to
<VPLink
class="notification-link"
href="https://v0.lucide.dev"
>v0 site</VPLink
>
</p>
</div>
</template>
<style>
:root {
--vp-layout-top-height: 64px;
}
@media (min-width: 640px) {
:root {
--vp-layout-top-height: 32px;
}
}
</style>
<style scoped>
.top-notification {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 9999;
display: flex;
height: var(--vp-layout-top-height);
justify-content: center;
align-items: center;
background: var(--vp-c-brand-dark);
color: var(--vp-c-bg-alt-down);
font-size: 14px;
padding: 0 16px;
}
.notification-link {
font-weight: 500;
text-decoration: underline;
}
.notification-link::after {
display: none !important;
}
@media (min-width: 640px) {
.top-notification br {
display: none;
}
}
</style>

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
export type IconNode = [elementName: string, attrs: Record<string, string>][]
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;
}>()
}>();
const icon = createLucideIcon(props.name, props.iconNode)
const icon = createLucideIcon(props.name, props.iconNode);
</script>
<template>

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"
>
<span class="title">
{{ title }}
</span>
<span
v-if="desc"
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);
margin-top: 2px;
}
.title {
display: block;
line-height: 20px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand-1);
transition: color 0.25s;
}
</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

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes';
import Icon from 'lucide-vue-next/src/Icon';
import Icon from '@lucide/vue/src/Icon';
import IconButton from './IconButton.vue';
</script>

View File

@@ -0,0 +1,239 @@
<script setup lang="ts">
import { ref } from 'vue';
import {
Listbox,
ListboxLabel,
ListboxButton,
ListboxOptions,
ListboxOption,
} from '@headlessui/vue';
import Icon from '@lucide/vue/src/Icon';
import { chevronsUpDown, check } from '../../../data/iconNodes';
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">
<Icon
:iconNode="chevronsUpDown"
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"
>
<Icon
:iconNode="check"
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 0.25s,
color 0.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,70 @@
<script lang="ts" setup>
import { type FunctionalComponent } from 'vue';
import { type LucideProps } from '@lucide/vue';
import { Sandpack } from 'sandpack-vue3';
defineProps<FunctionalComponent<LucideProps>>();
</script>
<template>
<Sandpack />
</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,92 @@
<script lang="ts" setup>
import { type SandpackFiles } from 'sandpack-vue3';
import styledCSS from '../../sandpack-default.css?raw';
import Sandpack from './Sandpack.vue';
const props = defineProps<{
files: SandpackFiles;
}>();
const angularFiles = {
'/src/index.html': {
code: `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Angular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<app></app>
</body>
</html>`,
hidden: true,
},
'/src/app/app.component.css': {
code: styledCSS,
hidden: true,
},
'/src/polyfills.ts': {
code: '',
hidden: true,
},
'/src/app/app.config.ts': {
code: `import { ApplicationConfig } from '@angular/core';
export const appConfig: ApplicationConfig = {
providers: []
};
`,
hidden: true,
},
'/src/main.ts': {
code: `import "@angular/compiler";
import { bootstrapApplication } from '@angular/platform-browser';
import { App } from './app/app.component';
import { appConfig } from './app/app.config';
void bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));`,
hidden: true,
},
'/package.json': {
code: JSON.stringify({
dependencies: {
'@angular/common': '^21.0.0',
'@angular/compiler': '^21.0.0',
'@angular/core': '^21.0.0',
'@angular/forms': '^21.0.0',
'@angular/platform-browser': '^21.0.0',
'@angular/router': '^21.0.0',
'zone.js': '0.16.1',
'core-js': '3.48.0',
rxjs: '7.4.0',
},
main: '/src/main.ts',
devDependencies: {
'@angular/build': '^21.0.3',
'@angular/cli': '^21.0.3',
'@angular/compiler-cli': '^21.0.0',
},
}),
hidden: true,
},
};
</script>
<template>
<Sandpack
template="angular"
:files="{
...angularFiles,
...props.files,
}"
/>
</template>

View File

@@ -0,0 +1,22 @@
<script lang="ts" setup>
import { type SandpackFiles } from 'sandpack-vue3';
import styledCSS from '../../sandpack-default.css?raw';
import Sandpack from './Sandpack.vue';
const props = defineProps<{
files: SandpackFiles;
}>();
</script>
<template>
<Sandpack
template="astro"
:files="{
...props.files,
'/src/styles.css': {
code: styledCSS,
hidden: true,
},
}"
/>
</template>

View File

@@ -0,0 +1,19 @@
<script lang="ts" setup>
import { SandpackFiles } from 'sandpack-vue3';
import { preactFiles } from './preact/files';
import Sandpack from './Sandpack.vue';
const props = defineProps<{
files: SandpackFiles;
}>();
</script>
<template>
<Sandpack
template="vanilla"
:files="{
...props.files,
...preactFiles,
}"
/>
</template>

View File

@@ -0,0 +1,43 @@
<script lang="ts" setup>
import { SandpackFiles, SANDBOX_TEMPLATES } from 'sandpack-vue3';
import Sandpack from './Sandpack.vue';
const props = defineProps<{
files: SandpackFiles;
}>();
</script>
<template>
<Sandpack
template="vite-solid"
:files="{
...props.files,
...SANDBOX_TEMPLATES['vite-solid'].files,
'/package.json': {
code: JSON.stringify(
{
type: 'module',
scripts: {
dev: 'vite',
build: 'tsc && vite build',
serve: 'vite preview',
},
dependencies: {
'solid-js': '^1.7.5',
'lucide-solid': 'latest',
},
devDependencies: {
vite: '4.2.2',
typescript: '^5.0.4',
'vite-plugin-solid': '^2.7.0',
'esbuild-wasm': '^0.17.19',
},
main: '/index.tsx',
},
null,
2,
),
},
}"
/>
</template>

View File

@@ -0,0 +1,59 @@
<script lang="ts" setup>
import { type SandpackFiles } from 'sandpack-vue3';
import styledCSS from '../../sandpack-default.css?raw';
import Sandpack from './Sandpack.vue';
const props = defineProps<{
files: SandpackFiles;
}>();
</script>
<template>
<Sandpack
template="vite-svelte"
:files="{
...props.files,
'/src/styles.css': {
code: styledCSS,
hidden: true,
},
'/package.json': {
hidden: true,
code: JSON.stringify(
{
type: 'module',
scripts: {
dev: 'vite',
},
dependencies: {
svelte: '^4.2.19',
'lucide-svelte': 'latest',
},
devDependencies: {
'@sveltejs/vite-plugin-svelte': '2.1.1',
vite: '4.2.2',
'esbuild-wasm': '^0.17.19',
},
},
null,
2,
),
},
'/vite.config.js': {
hidden: true,
code: `import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
resolve: {
alias: {
'@lucide/svelte': 'lucide-svelte',
},
},
})`,
},
}"
/>
</template>

View File

@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { ComponentPropsOptions, type FunctionalComponent } from 'vue';
import { SandpackFiles, SANDBOX_TEMPLATES } from 'sandpack-vue3';
import Sandpack from './Sandpack.vue';
import styledCSS from '../../sandpack-default.css?raw';
const props = defineProps<{
files: SandpackFiles;
}>();
</script>
<template>
<Sandpack
template="vue"
:files="{
...props.files,
'/src/styles.css': {
code: styledCSS,
hidden: true,
},
}"
/>
</template>

View File

@@ -0,0 +1,53 @@
<script setup lang="ts">
import { useMutationObserver, useScriptTag } from '@vueuse/core';
import { useData } from 'vitepress';
import { onMounted, useTemplateRef } from 'vue';
const { isDark } = useData();
const el = useTemplateRef('el');
useScriptTag('https://snack.expo.dev/embed.js');
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,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

@@ -38,7 +38,7 @@ button {
}
button:hover {
background: #F56565;
background: #f56565;
}
.app {

View File

@@ -0,0 +1,119 @@
<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/' },
{ name: 'Static', icon: '/framework-logos/svg.svg', route: '/guide/static/' },
];
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1]);
const selected = computed(() => {
const current = frameworks.find(({ route }) => {
if (router.route.path?.startsWith?.('/guide')) {
const [, , framework] = router.route.path.split('/');
const [, frameWorkRoute] = route.split('/').filter(Boolean);
return framework === frameWorkRoute;
}
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);
console.log(likeRoute);
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

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

View File

@@ -0,0 +1,678 @@
<script setup lang="ts">
import { ref, shallowRef } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import { useRouter } from 'vitepress';
import { motion, Variants, useScroll, useTransform } from 'motion-v';
import LucideIcon from '../base/LucideIcon.vue';
const emit = defineEmits(['animation-complete']);
const MotionLucideIcon = motion.create(LucideIcon);
const preventFocusProps = {
tabindex: '-1',
focusable: 'false',
};
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.02,
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.02,
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"
v-bind="preventFocusProps"
>
<div
class="home-hero-animation"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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"
v-bind="preventFocusProps"
>
<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
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"
/>
</g>
<motion.g
initial="initial"
:variants="shrinkIconVariants"
:animate="shrinkIconAnimation"
@animation-complete="onAnimationComplete"
v-bind="preventFocusProps"
>
<MotionLucideIcon
v-for="(icon, index) in icons"
size="2"
initial="initial"
:key="icon.name"
:variants="iconAnimationVariants"
:animate="iconGridAnimation"
:custom="index"
strokeWidth="inherit"
v-bind="icon"
@animation-complete="onAnimationComplete"
tabindex="-1"
focusable="false"
/>
<motion.g
:variants="scaleDownVariants"
:animate="scaleDownAnimation"
initial="hidden"
@animation-complete="onAnimationComplete"
v-bind="preventFocusProps"
>
<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"
v-bind="preventFocusProps"
/>
<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"
v-bind="preventFocusProps"
/>
</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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
/>
<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 }"
v-bind="preventFocusProps"
/>
<motion.g
:initial="{ opacity: 0 }"
:animate="{ opacity: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<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"
v-bind="preventFocusProps"
/>
<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"
v-bind="preventFocusProps"
/>
<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"
v-bind="preventFocusProps"
/>
<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"
v-bind="preventFocusProps"
/>
<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"
v-bind="preventFocusProps"
/>
</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 }"
v-bind="preventFocusProps"
>
<motion.g
:initial="{ opacity: 0, scale: 0.2 }"
:animate="{ opacity: 1, scale: 1 }"
:transition="{ delay: 0.2, duration: 0.3 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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 }"
v-bind="preventFocusProps"
>
<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;
pointer-events: none;
}
@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: -420px;
margin-top: -64px;
/* margin-right: calc(((((100vw - 1152px) / 2)) * -1) + 24px); */
margin-left: -128px;
}
.hero-background {
width: 1024px;
height: 1024px;
top: -280px;
}
}
</style>

View File

@@ -1,16 +1,97 @@
import iconNodes from '../../../data/iconNodes';
const getRandomItem = <Item>(items: Item[]): Item =>
items[Math.floor(Math.random() * items.length)];
const pickIcons = [
'bot',
'camera',
'car',
'cloud',
'code',
'coffee',
'compass',
'cpu',
'disc',
'droplet',
'eye',
'bug',
'house',
'heart-pulse',
'clapperboard',
'tool-case',
'printer',
'album',
'pencil-ruler',
'calendar',
'brain',
'audio-waveform',
'bandage',
'plane-takeoff',
'ferris-wheel',
'router',
'file-box',
'folder-tree',
'banknote',
'cake',
'rocket',
'picture-in-picture-2',
'database',
'messages-square',
'map-pinned',
'megaphone',
'moon',
'music',
'phone',
'power-off',
'radio',
'refresh-ccw',
'repeat',
'rss',
'shield',
'shopping-cart',
'speakerphone',
'star',
'sun',
'terminal',
'thermometer',
'thumbs-up',
'trash-2',
'umbrella',
'watch',
'sparkles',
'wifi',
'tent-tree',
'traffic-cone',
'tree-deciduous',
'trophy',
'tv',
'windmill',
'wrench',
'satellite',
'stethoscope',
];
export default {
async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
const randomIcons = Array.from({ length: 200 }, () => getRandomItem(icons));
const selectedIcons = icons.filter(({ name }) => pickIcons.includes(name)).reverse();
const hasDuplicates =
new Set(selectedIcons.map(({ name }) => name)).size !== selectedIcons.length;
if (hasDuplicates) {
throw new Error(
`Duplicate icons found in the selected icons. Please check the pickIcons array and the iconNodes data for duplicates.`,
);
}
if (selectedIcons.length !== 64) {
throw new Error(
`Expected to find 64 icons, but found ${selectedIcons.length}. Please check the pickIcons array and the iconNodes data.`,
);
}
return {
icons: randomIcons,
icons: selectedIcons,
iconsCount: icons.length,
};
},

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

@@ -0,0 +1,85 @@
<script setup lang="ts">
import Badge from '../base/Badge.vue';
import { moveRight } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import { data } from './HomeHeroInfoBefore.data';
const rocketDuoTone = [
[
'path',
{
d: 'M8 18a2 2 0 0 0-2-2c-2.53-.078-3.247 3.605-3.5 5.5 1.895-.253 5.578-.97 5.5-3.5',
fill: 'currentColor',
opacity: 0.5,
},
],
[
'path',
{
d: 'M22 2c0 2.72-.78 7.5-6 11 0 0 1.08 3.38 0 5-.969 1.448-4 2-4 2v-5l-3-3H4s.553-3.033 2-4c1.616-1.077 4.982.043 5 .05A12.88 12.88 0 0 1 22 2m-6 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6',
fill: 'currentColor',
'fill-rule': 'evenodd',
'clip-rule': 'evenodd',
},
],
] as const;
</script>
<template>
<div class="info">
<Badge :href="`https://github.com/lucide-icons/lucide/releases/tag/${data.version}`">
v{{ data.version }}
</Badge>
<Badge
class="badge-special confetti-button animate"
href="/guide/version-1"
>
<Icon :iconNode="rocketDuoTone" />
Version 1
<Icon
:iconNode="moveRight"
class=""
/>
</Badge>
</div>
</template>
<style scoped>
.info {
display: flex;
gap: 16px;
margin-bottom: 16px;
justify-content: center;
}
@media (min-width: 960px) {
.info {
justify-content: flex-start;
}
}
.badge {
display: inline-flex;
gap: 8px;
padding: 4px 8px;
}
.badge-special {
font-weight: 500;
color: var(--vp-c-brand-1);
text-underline-offset: 2px;
align-items: center;
}
.badge-special::before,
.badge-special::after {
animation-delay: 0.5s !important;
animation-duration: 5s !important;
animation-iteration-count: infinite !important;
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1) !important;
transform: translateX(32px);
}
</style>
<style>
@import '../icons/confetti.css';
</style>

View File

@@ -6,47 +6,56 @@ export default {
name: 'lucide',
logo: '/framework-logos/js.svg',
label: 'Lucide documentation for JavaScript',
path: '/guide/lucide/',
},
{
name: 'lucide-react',
logo: '/framework-logos/react.svg',
label: 'Lucide documentation for React',
path: '/guide/react/',
},
{
name: 'lucide-vue',
logo: '/framework-logos/vue.svg',
label: 'Lucide documentation for Vue',
path: '/guide/vue/',
},
{
name: 'lucide-svelte',
logo: '/framework-logos/svelte.svg',
label: 'Lucide documentation for Svelte',
path: '/guide/svelte/',
},
{
name: 'lucide-solid',
logo: '/framework-logos/solid.svg',
label: 'Lucide documentation for Solid',
path: '/guide/solid/',
},
{
name: 'lucide-preact',
logo: '/framework-logos/preact.svg',
label: 'Lucide documentation for Preact',
path: '/guide/preact/',
},
{
name: 'angular',
logo: '/framework-logos/angular.svg',
label: 'Lucide documentation for Angular',
path: '/guide/angular/',
},
{
name: 'lucide-astro',
logo: '/framework-logos/astro.svg',
logoDark: '/framework-logos/astro-dark.svg',
label: 'Lucide documentation for Astro',
path: '/guide/astro/',
},
{
name: 'lucide-react-native',
logo: '/framework-logos/react-native.svg',
label: 'Lucide documentation for React Native',
path: '/guide/react-native/',
},
],
};

View File

@@ -4,7 +4,7 @@ import { useSessionStorage } from '@vueuse/core';
import IconButton from '../base/IconButton.vue';
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
import { x } from '../../../data/iconNodes';
import Icon from 'lucide-vue-next/src/Icon';
import Icon from '@lucide/vue/src/Icon';
import { onMounted, ref } from 'vue';
const { theme } = useData();

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { computed, useSlots } from 'vue';
import { copy } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import useConfetti from '../../composables/useConfetti';
import Icon from 'lucide-vue-next/src/Icon';
const { animate, confetti } = useConfetti();
const slots = useSlots();

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { IconEntity } from '../../types'
import { computed } from 'vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import IconButton from '../base/IconButton.vue';
import IconContributors from './IconContributors.vue';
import IconPreview from './IconPreview.vue';
import { x, expand } from '../../../data/iconNodes'
import { x, expand } from '../../../data/iconNodes';
import { useRouter } from 'vitepress';
import IconInfo from './IconInfo.vue';
import Badge from '../base/Badge.vue';
@@ -14,63 +14,79 @@ import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
const props = defineProps<{
iconName: string | null
}>()
iconName: string | null;
}>();
const { externalIconNodes } = useExternalLibs()
const { externalIconNodes } = useExternalLibs();
const { go } = useRouter()
const { go } = useRouter();
const icon = computedAsync<IconEntity | null>(async () => {
if (props.iconName) {
try {
if (props.iconName.includes(':')) {
const [library, name] = props.iconName.split(':')
const [library, name] = props.iconName.split(':');
return externalIconNodes.value[library].find((icon) => icon.name === name)
return externalIconNodes.value[library].find((icon) => icon.name === name);
} else {
return (await import(`../../../data/iconDetails/${props.iconName}.ts`)).default as IconEntity
return (await import(`../../../data/iconDetails/${props.iconName}.ts`))
.default as IconEntity;
}
} catch (err) {
if (!props.iconName.includes(':')) {
go(`/icons/${props.iconName}`)
go(`/icons/${props.iconName}`);
}
}
}
return null
}, null)
return null;
}, null);
const emit = defineEmits(['close'])
const isOpen = computed(() => !!icon.value)
const emit = defineEmits(['close']);
const isOpen = computed(() => !!icon.value);
function releaseTagLink(version) {
const shouldAddV = satisfies(version, `<0.266.0`)
const shouldAddV = satisfies(version, `<0.266.0`);
return `https://github.com/lucide-icons/lucide/releases/tag/${shouldAddV ? 'v' : ''}${version}`
return `https://github.com/lucide-icons/lucide/releases/tag/${shouldAddV ? 'v' : ''}${version}`;
}
function onClose() {
emit('close')
emit('close');
}
const CloseIcon = createLucideIcon('Close', x)
const Expand = createLucideIcon('Expand', expand)
const CloseIcon = createLucideIcon('Close', x);
const Expand = createLucideIcon('Expand', expand);
</script>
<template>
<Transition name="drawer" appear>
<div class="overlay-container" v-if="icon">
<Transition
name="drawer"
appear
>
<div
class="overlay-container"
v-if="icon"
>
<div class="overlay-panel">
<nav class="overlay-menu">
<Badge
v-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
>v{{ icon.createdRelease.version }}</Badge>
<IconButton @click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}` : `/icons/${icon.name}`)">
>v{{ icon.createdRelease.version }}</Badge
>
<IconButton
@click="
go(
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`,
)
"
>
<component :is="Expand" />
</IconButton>
<IconButton @click="onClose">
<IconButton @click="onClose">
<component :is="CloseIcon" />
</IconButton>
</nav>
@@ -80,9 +96,15 @@ const Expand = createLucideIcon('Expand', expand)
:iconNode="icon.iconNode"
customizable
/>
<IconInfo :icon="icon" popoverPosition="top">
<IconInfo
:icon="icon"
popoverPosition="top"
>
<template v-slot:footer>
<IconContributors :icon="icon" class="contributors" />
<IconContributors
:icon="icon"
class="contributors"
/>
</template>
</IconInfo>
</div>
@@ -157,11 +179,15 @@ const Expand = createLucideIcon('Expand', expand)
}
.drawer-enter-active {
transition: opacity 0.5s, transform 0.25s ease;
transition:
opacity 0.5s,
transform 0.25s ease;
}
.drawer-leave-active {
transition: opacity 0.25s ease, transform 1.6s ease-out;
transition:
opacity 0.25s ease,
transform 1.6s ease-out;
}
.drawer-enter-from,
@@ -177,5 +203,4 @@ const Expand = createLucideIcon('Expand', expand)
.contributors {
justify-content: flex-end;
}
</style>

View File

@@ -5,27 +5,26 @@ import Badge from '../base/Badge.vue';
import CopySVGButton from './CopySVGButton.vue';
import CopyCodeButton from './CopyCodeButton.vue';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import {useData, useRouter} from 'vitepress';
import { useData, useRouter } from 'vitepress';
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes'
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes';
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
const props = defineProps<{
icon: IconEntity
popoverPosition?: 'top' | 'bottom'
}>()
icon: IconEntity;
popoverPosition?: 'top' | 'bottom';
}>();
const { go } = useRouter()
const { page } = useData()
const { go } = useRouter();
const { page } = useData();
const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return []
return props.icon.tags.join(' • ')
})
if (!props.icon || !props?.icon?.tags) return [];
return props.icon.tags.join(' • ');
});
const DiamondIcon = createLucideIcon('Diamond', diamond)
const DiamondIcon = createLucideIcon('Diamond', diamond);
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
@@ -43,8 +42,14 @@ const deprecatedTitle = computed(() => {
<IconDetailName class="icon-name">
{{ icon.name }}
</IconDetailName>
<div v-if="icon.externalLibrary" class="icon-external-lib">
<DiamondIcon fill="currentColor" :size="12"/>
<div
v-if="icon.externalLibrary"
class="icon-external-lib"
>
<DiamondIcon
fill="currentColor"
:size="12"
/>
{{ icon.externalLibrary }}
</div>
<Badge
@@ -55,7 +60,10 @@ const deprecatedTitle = computed(() => {
Deprecated
</Badge>
</div>
<div class="tags-scroller" v-if="tags.length">
<div
class="tags-scroller"
v-if="tags.length"
>
<p class="icon-tags horizontal-scroller">
{{ tags }}
</p>
@@ -72,13 +80,35 @@ const deprecatedTitle = computed(() => {
<div class="group buttons">
<VPButton
v-if="!page?.relativePath?.startsWith?.(icon.externalLibrary ? `icons/${icon.externalLibrary}/${icon.name}`: `icons/${icon.name}`)"
:href="icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`"
v-if="
!page?.relativePath?.startsWith?.(
icon.externalLibrary
? `icons/${icon.externalLibrary}/${icon.name}`
: `icons/${icon.name}`,
)
"
:href="
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`
"
text="See in action"
@click="go(icon.externalLibrary ? `/icons/${icon.externalLibrary}/${icon.name}`: `/icons/${icon.name}`)"
@click="
go(
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`,
)
"
/>
<CopySVGButton
:name="icon.name"
:popoverPosition="popoverPosition"
/>
<CopyCodeButton
:name="icon.name"
:popoverPosition="popoverPosition"
/>
<CopySVGButton :name="icon.name" :popoverPosition="popoverPosition"/>
<CopyCodeButton :name="icon.name" :popoverPosition="popoverPosition"/>
</div>
<slot name="footer" />
</div>
@@ -119,7 +149,7 @@ const deprecatedTitle = computed(() => {
.deprecated-badge {
background-color: var(--vp-c-brand-5);
margin-left: 40px;
opacity: .8;
opacity: 0.8;
}
.deprecated-badge:hover {
@@ -150,7 +180,7 @@ const deprecatedTitle = computed(() => {
margin-top: 8px;
align-items: center;
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv))
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv));
}
.horizontal-scroller {
overflow-x: scroll;
@@ -162,19 +192,18 @@ const deprecatedTitle = computed(() => {
}
.horizontal-scroller::-webkit-scrollbar {
width: 0;
display: none
display: none;
}
.horizontal-scroller::-webkit-scrollbar-track {
background: transparent
background: transparent;
}
.horizontal-scroller::-webkit-scrollbar-thumb {
background: transparent;
border: none
border: none;
}
.tags-scroller::after {
content: '';
position: absolute;
@@ -182,7 +211,7 @@ const deprecatedTitle = computed(() => {
width: 32px;
height: 100%;
/* Background Gradient left to right */
background: linear-gradient(to right, rgba(255,255,255,0) 0%,var(--gradient-background) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--gradient-background) 100%);
right: 0;
pointer-events: none;
}

View File

@@ -1,17 +1,17 @@
<script setup lang="ts">
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useMediaQuery } from '@vueuse/core';
import { useRouter } from 'vitepress';
import getSVGIcon from '../../utils/getSVGIcon';
import useConfetti from '../../composables/useConfetti';
import Tooltip from '../base/Tooltip.vue';
import { diamond } from '../../../data/iconNodes'
import { diamond } from '../../../data/iconNodes';
const downloadText = 'Download!'
const copiedText = 'Copied!'
const downloadText = 'Download!';
const copiedText = 'Copied!';
export type IconNode = [elementName: string, attrs: Record<string, string>][]
export type IconNode = [elementName: string, attrs: Record<string, string>][];
const props = defineProps<{
name: string;
@@ -19,50 +19,64 @@ const props = defineProps<{
active: boolean;
externalLibrary?: string;
customizable?: boolean;
overlayMode?: boolean
hideIcon?: boolean
}>()
overlayMode?: boolean;
hideIcon?: boolean;
}>();
const emit = defineEmits(['setActiveIcon'])
const emit = defineEmits(['setActiveIcon']);
const { go } = useRouter()
const { go } = useRouter();
const showOverlay = useMediaQuery('(min-width: 860px)');
const { animate, confetti, confettiText } = useConfetti()
const { animate, confetti, confettiText } = useConfetti();
const icon = computed(() => {
if (!props.name || !props.iconNode) return null
return createLucideIcon(props.name, props.iconNode)
})
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
const href = computed(() => props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
const href = computed(() =>
props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`,
);
async function navigateToIcon(event) {
if (event.shiftKey) {
event.preventDefault()
event.preventDefault();
const svgString = getSVGIcon(event.target.firstChild, {
class: `lucide lucide-${props.name}`,
})
});
await navigator.clipboard.writeText(svgString)
await navigator.clipboard.writeText(svgString);
confettiText.value = copiedText
confetti()
return
confettiText.value = copiedText;
confetti();
return;
}
if(props.overlayMode && showOverlay.value) {
event.preventDefault()
if (props.overlayMode && showOverlay.value) {
event.preventDefault();
window.history.pushState({}, '', props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
emit('setActiveIcon', props.externalLibrary ? `${props.externalLibrary}:${props.name}`: props.name)
window.history.pushState(
{},
'',
props.externalLibrary
? `/icons/${props.externalLibrary}/${props.name}`
: `/icons/${props.name}`,
);
emit(
'setActiveIcon',
props.externalLibrary ? `${props.externalLibrary}:${props.name}` : props.name,
);
} else {
event.preventDefault()
go(props.externalLibrary ? `/icons/${props.externalLibrary}/${props.name}` : `/icons/${props.name}`)
event.preventDefault();
go(
props.externalLibrary
? `/icons/${props.externalLibrary}/${props.name}`
: `/icons/${props.name}`,
);
}
}
const DiamondIcon = createLucideIcon('Diamond', diamond)
const DiamondIcon = createLucideIcon('Diamond', diamond);
</script>
<template>
@@ -72,7 +86,6 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
@click="navigateToIcon"
:class="{ active, animate }"
:aria-label="name"
:data-confetti-text="confettiText"
ref="ref"
>
@@ -91,7 +104,10 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
class="floating-diamond"
aria-hidden="true"
>
<DiamondIcon fill="currentColor" :size="8"/>
<DiamondIcon
fill="currentColor"
:size="8"
/>
</div>
</a>
</Tooltip>
@@ -108,7 +124,10 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
font-weight: 600;
border-radius: 4px;
white-space: nowrap;
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
display: inline-flex;
@@ -135,7 +154,10 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
}
.icon-button:active {
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
transition:
color 0.1s,
border-color 0.1s,
background-color 0.1s;
}
.icon-button.medium {
@@ -159,9 +181,9 @@ const DiamondIcon = createLucideIcon('Diamond', diamond)
}
.icon-button:active {
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
border-color: var(--vp-button-alt-active-border);
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
}
.icon-button.active {

View File

@@ -1,24 +1,24 @@
<script setup lang="ts">
import type { IconEntity } from '../../types'
import { computed, ref } from 'vue'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import type { IconEntity } from '../../types';
import { computed, ref } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useIconStyleContext } from '../../composables/useIconStyle';
const props = defineProps<{
name: IconEntity['name']
iconNode: IconEntity['iconNode']
customizable?: boolean
}>()
name: IconEntity['name'];
iconNode: IconEntity['iconNode'];
customizable?: boolean;
}>();
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext()
const previewIcon = ref()
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const previewIcon = ref();
const gridLines = computed(() => Array.from({ length:(size.value - 1) }))
const gridLines = computed(() => Array.from({ length: size.value - 1 }));
const iconComponent = computed(() => {
if (!props.name || !props.iconNode) return null
return createLucideIcon(props.name, props.iconNode)
})
if (!props.name || !props.iconNode) return null;
return createLucideIcon(props.name, props.iconNode);
});
</script>
<template>
@@ -28,12 +28,33 @@ const iconComponent = computed(() => {
:is="iconComponent"
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth"
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
/>
<svg class="icon-grid" :viewBox="`0 0 ${size} ${size}`" fill="none" stroke-width="0.1" xmlns="http://www.w3.org/2000/svg">
<g :key="`grid-${i}`" v-for="(_, i) in gridLines">
<line :key="`horizontal-${i}`" :x1="0" :y1="i + 1" :x2="size" :y2="i + 1" />
<line :key="`vertical-${i}`" :x1="i + 1" y1="0" :x2="i + 1" :y2="size" />
<svg
class="icon-grid"
:viewBox="`0 0 ${size} ${size}`"
fill="none"
stroke-width="0.1"
xmlns="http://www.w3.org/2000/svg"
>
<g
:key="`grid-${i}`"
v-for="(_, i) in gridLines"
>
<line
:key="`horizontal-${i}`"
:x1="0"
:y1="i + 1"
:x2="size"
:y2="i + 1"
/>
<line
:key="`vertical-${i}`"
:x1="i + 1"
y1="0"
:x2="i + 1"
:y2="size"
/>
</g>
</svg>
</div>

View File

@@ -1,27 +1,32 @@
<script setup lang="ts">
import type { IconEntity } from '../../types'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import type { IconEntity } from '../../types';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
const props = defineProps<{
name: IconEntity['name']
iconNode: IconEntity['iconNode']
customizable?: boolean
}>()
const Icon = createLucideIcon(props.name, props.iconNode)
name: IconEntity['name'];
iconNode: IconEntity['iconNode'];
customizable?: boolean;
}>();
const Icon = createLucideIcon(props.name, props.iconNode);
</script>
<template>
<div class="icons-small-preview">
<div class="icon-wrapper">
<Icon :size="48" class="lucide-icon"/>
<Icon
:size="48"
class="lucide-icon"
/>
</div>
<div class="icon-wrapper">
<Icon :size="32" class="lucide-icon"/>
<Icon
:size="32"
class="lucide-icon"
/>
</div>
<div class="icon-wrapper">
<Icon class="lucide-icon"/>
<Icon class="lucide-icon" />
</div>
</div>
</template>
@@ -42,7 +47,10 @@ const Icon = createLucideIcon(props.name, props.iconNode)
font-weight: 600;
border-radius: 4px;
white-space: nowrap;
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
transition:
color 0.25s,
border-color 0.25s,
background-color 0.25s;
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
display: inline-flex;

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon.ts';
import createLucideIcon from '@lucide/vue/src/createLucideIcon.ts';
import Calendar from '../../../data/iconDetails/calendar.ts';
import Clock from '../../../data/iconDetails/clock.ts';
import Bug from '../../../data/iconDetails/bug.ts';
@@ -66,7 +66,10 @@ const prettyName = props.name
</script>
<template>
<section class="showcase" v-if="iconComponent">
<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

@@ -185,5 +185,6 @@ function handleCloseDrawer() {
.input-wrapper {
width: 100%;
/* view-transition-name: icons-search-box; */
}
</style>

View File

@@ -20,7 +20,7 @@ import {
testTubeDiagonal,
sword,
} from '../../../data/iconNodes';
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { useEventListener } from '@vueuse/core';
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
import { IconNode } from '../../types';

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,57 +1,90 @@
<script setup lang="ts">
import { useData } from 'vitepress'
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { computed } from 'vue'
import { useData } from 'vitepress';
import { useSidebar } from 'vitepress/dist/client/theme-default/composables/sidebar';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import { computed } from 'vue';
const { theme } = useData()
const { hasSidebar } = useSidebar()
const { theme } = useData();
const { hasSidebar } = useSidebar();
const githubLink = computed(() => theme.value.socialLinks.find(({icon}) => icon === 'github').link)
const githubLink = computed(
() => theme.value.socialLinks.find(({ icon }) => icon === 'github').link,
);
const links = computed(() => [
{
text: 'License',
href: '/license'
href: '/license',
},
{
text: 'Contribute',
href: '/contributing'
href: '/contribute/',
},
{
text: 'Changelog',
href: `${githubLink.value}/releases`
href: `${githubLink.value}/releases`,
},
{
text: 'GitHub',
href: `${githubLink.value}`
href: `${githubLink.value}`,
},
{
text: 'Issues',
href: `${githubLink.value}/issues`
}
])
href: `${githubLink.value}/issues`,
},
]);
</script>
<template>
<footer v-if="theme.footer" class="VPFooter" :class="{ 'has-sidebar': hasSidebar }">
<footer
v-if="theme.footer"
class="VPFooter"
:class="{ 'has-sidebar': hasSidebar }"
>
<div class="container">
<div class="sponsors">
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss" rel="noreferrer noopener">
<img src="/vercel.svg" alt="Powered by Vercel" width="200" />
<a
href="https://vercel.com?utm_source=lucide&utm_campaign=oss"
rel="noreferrer noopener"
>
<img
src="/vercel.svg"
alt="Powered by Vercel"
width="200"
/>
</a>
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge" rel="noreferrer noopener">
<img src="/digitalocean.svg" alt="Digital Ocean" width="200" />
<a
href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"
rel="noreferrer noopener"
>
<img
src="/digitalocean.svg"
alt="Digital Ocean"
width="200"
/>
</a>
</div>
<div class="links">
<VPLink v-for="link in links" :href="link.href" :key="link.text" :rel="link.href.startsWith('http') ? 'noreferrer noopener': undefined">
<VPLink
v-for="link in links"
:href="link.href"
:key="link.text"
:rel="link.href.startsWith('http') ? 'noreferrer noopener' : undefined"
>
{{ link.text }}
</VPLink>
</div>
<div>
<p v-if="theme.footer.message" class="message" v-html="theme.footer.message"></p>
<p v-if="theme.footer.copyright" class="copyright" v-html="theme.footer.copyright"></p>
<p
v-if="theme.footer.message"
class="message"
v-html="theme.footer.message"
></p>
<p
v-if="theme.footer.copyright"
class="copyright"
v-html="theme.footer.copyright"
></p>
</div>
</div>
</footer>
@@ -70,8 +103,6 @@ const links = computed(() => [
display: none;
}
.container {
margin: 0 auto;
max-width: calc(var(--vp-layout-max-width) - 64px);
@@ -89,10 +120,15 @@ const links = computed(() => [
color: var(--vp-c-text-2);
}
.message { order: 2; }
.copyright { order: 1; }
.message {
order: 2;
}
.copyright {
order: 1;
}
.links, .sponsors {
.links,
.sponsors {
display: flex;
gap: 32px;
align-items: center;

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { menu } from '../../../data/iconNodes'
import createLucideIcon from 'lucide-vue-next/src/createLucideIcon'
import { menu } from '../../../data/iconNodes';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
const Menu = createLucideIcon('menu', menu)
const Menu = createLucideIcon('menu', menu);
</script>
<template>

View File

@@ -14,7 +14,8 @@ export default {
return {
...pJson,
...packageData,
documentation: `/guide/packages/${packageData.docsAlias ?? pJson.name}`,
documentation:
packageData.documentation ?? `/guide/${packageData.docsAlias ?? pJson.name}`,
source: `https://github.com/lucide-icons/lucide/tree/main/packages/${packageData.packageDirname ?? pJson.name}`,
icon: `/framework-logos/${packageData.icon}.svg`,
iconDark: Boolean(packageData.iconDark)
@@ -22,6 +23,7 @@ export default {
: null,
};
})
.filter((pData) => !pData.hide)
.sort((a, b) => a.order - b.order),
thirdPartyPackages,
};

View File

@@ -1,27 +1,36 @@
import { h } from 'vue';
import DefaultTheme from 'vitepress/theme';
import './style.css';
import 'virtual:group-icons.css';
import { Theme } from 'vitepress';
import IconsSidebarNavAfter from './layouts/IconsSidebarNavAfter.vue';
import HomeHeroIconsCard from './components/home/HomeHeroIconsCard.vue';
import HomeHeroBefore from './components/home/HomeHeroBefore.vue';
import HomeHeroAfter from './components/home/HomeHeroAfter.vue';
import HomeHeroInfoBefore from './components/home/HomeHeroInfoBefore.vue';
import LayoutTop from './components/base/LayoutTop.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';
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),
'layout-top': () => h(LayoutTop),
});
},
enhanceApp({ app }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
app.component('SnackPlayer', SnackPlayer);
},
};

View File

@@ -0,0 +1,55 @@
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,
#app {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 32px;
}
svg {
display: block;
}

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 {
@@ -42,7 +48,27 @@
--vp-code-editor-static: #f78c6c;
--vp-code-editor-string: #9ecbff;
--vp-c-text-1: #dfdfd6;
--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;
} */
.VPHome {
overflow-x: hidden;
}
.VPNavBarTitle .logo {
@@ -70,7 +96,7 @@
.VPHomeHero .container .image {
margin: 0;
order: 2;
margin-top: 32px;
/* margin-top: 32px; */
}
.VPHomeHero .container .image-container {
@@ -91,6 +117,10 @@
padding-left: 0;
}
.VPHomeHero .container {
flex-direction: column-reverse;
}
@media (min-width: 960px) {
.VPHomeHero .container .image {
order: 1;
@@ -110,60 +140,17 @@
.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);
.package-lucide-logo {
margin: 0 auto 48px;
max-width: 480px;
}

View File

@@ -1,4 +1,4 @@
import { type IconNode } from 'lucide-vue-next/src/types';
import { type IconNode } from '@lucide/vue/src/types';
import Vue from 'vue';
declare module '*.vue' {
@@ -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;
@@ -23,3 +29,22 @@ declare module '*.node.json' {
const value: IconNode;
export default value;
}
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

@@ -0,0 +1,7 @@
---
editLink: false
title: Brand logo Statement
description: Lucide does not accept brand logos, and we do not plan to add them in the future. This is due to a combination of legal restrictions, design consistency concerns, and practical maintenance reasons.
---
<!--@include: ../BRAND_LOGOS_STATEMENT.md -->

View File

@@ -1,5 +1,7 @@
---
aside: false
editLink: false
title: Code of Conduct
description: This Code of Conduct outlines our expectations for participant behavior as well as the consequences for unacceptable behavior within our community. We are committed to providing a welcoming, safe, and inclusive environment for all members of our community.
---
<!--@include: ../CODE_OF_CONDUCT.md -->

49
docs/community.md Normal file
View File

@@ -0,0 +1,49 @@
# Community Guide
Welcome to the Lucide community! This guide provides information on how to get involved, contribute, and connect with other members of the Lucide community.
## Code of Conduct
Please read and adhere to our [Code of Conduct](/code-of-conduct.md) to ensure a welcoming and inclusive environment for all community members.
## Connecting with the Community
There are several ways to get involved with the Lucide community:
- **Join the Discord Server**: Connect with other Lucide users, ask questions, and share your projects on our [Discord server](https://discord.gg/EH6nSts).
- **Follow us on X**: Stay updated with the latest news and updates by following us on [X](https://x.com/lucide_icons).
- **Join discussions on GitHub**: Participate in discussions, provide feedback, and help others on our [GitHub Discussions page](https://github.com/lucide-icons/lucide/discussions).
## Getting Involved
- **Contribute to the Project**: Check out our [contribution guidelines](./contribute/index.md) to learn how you can contribute to the Lucide project.
- **Report Issues**: If you encounter any bugs or have feature requests, please report them on our [GitHub Issues page](https://github.com/lucide-icons/lucide/issues).
## What you can do
There are many ways to contribute to the Lucide community.
### Help other people
Contribution in form of code or new icons is always welcome, but you can also help by answering questions from other community members on Discord or GitHub Discussions.
### Design New Icons
If you have design skills, consider contributing new icons to the Lucide library. Check out our [icon design guide](/contribute/icon-design-guide.md) for tips and guidelines on creating icons that fit the Lucide style.
### Contribute Code
If you're a developer, you can contribute code to the Lucide project. Whether it's fixing bugs, adding new features, or improving documentation, your contributions are valuable. Check out our [contribution guidelines](/contribute/index.md) for more information.
### Triage Issues
You can help by reviewing and triaging issues on our GitHub repository. This includes confirming bugs, providing additional information, and helping to prioritize issues for the maintainers.
### Improve Documentation
Lucide's documentation is open for contributions. If you find any areas that could be improved or if you have ideas for new guides or tutorials, feel free to submit a pull request.
The documentation can be found in the [repository](https://github.com/lucide-icons/lucide/tree/main/docs)
### Share your experiences
Share your experiences using Lucide in your projects. Write blog posts, create tutorials, or share your projects on social media to help others discover and learn about Lucide.

View File

@@ -14,5 +14,5 @@ This guide describes how to use the Affinity Designer template for Lucide.
2. Follow the [Icon Design Principles](icon-design-guide.md) while you use the template (to ensure integrity with the Lucide icon pack).
3. Export the file as SVG (`File > Export`). Make sure that _Rastering_ is set to _Nothing_, _Export text as curves_ is checked (hopefully, you won't need this), _Use hex colors_ is checked, and _Flatten transforms_ is checked.
![SVG export options in Affinity Designer](../../images/affinity-designer-export-options.png?raw=true)
![SVG export options in Affinity Designer](../images/affinity-designer-export-options.png?raw=true)
4. Optimize the exported SVG file further with [SVGOMG](https://jakearchibald.github.io/svgomg/) or [`svgo`](https://github.com/svg/svgo) (using `svgo --multipass exported_icon.svg`).

View File

@@ -28,7 +28,7 @@ Set the following:
1. Stroke width: 2px
2. Stroke alignment: center
![Figma Stroke Options](../../images/figma-stroke-options.png)
![Figma Stroke Options](../images/figma-stroke-options.png)
## Export Or Copy Your Icon
Once you have completed your icon, you can export it.

View File

@@ -1,6 +1,9 @@
---
title: Icon Design Guide
description: Guidelines and best practices for designing icons for the Lucide icon library.
---
# Icon Design Guide
Guidelines and best practices for designing icons for the Lucide icon library.
## Icon Design Principles
@@ -8,85 +11,85 @@ Here are rules that should be followed to keep quality and consistency when maki
### 1. Icons must be designed on a **24 by 24 pixels** canvas.
![24px-24px](../../images/24px-24px.svg?raw=true "24px-24px")
![24px-24px](../images/24px-24px.svg?raw=true "24px-24px")
### 2. Icons must have at least **1 pixel padding** within the canvas.
![1px-padding](../../images/1px-padding.svg?raw=true "1px-padding")
![1px-padding](../images/1px-padding.svg?raw=true "1px-padding")
### 3. Icons must have a **stroke width of 2 pixels**.
![2px-stroke](../../images/2px-stroke.svg?raw=true "2px-stroke")
![2px-stroke](../images/2px-stroke.svg?raw=true "2px-stroke")
### 4. Icons must use **round joins**.
![round-joints](../../images/round-joints.svg?raw=true "round-joints")
![round-joints](../images/round-joints.svg?raw=true "round-joints")
### 5. Icons must use **round caps**.
![round-caps](../../images/round-caps.svg?raw=true "round-caps")
![round-caps](../images/round-caps.svg?raw=true "round-caps")
### 6. Icons must use **centered strokes**.
![centered-strokes](../../images/centered-strokes.svg?raw=true "centered-strokes")
![centered-strokes](../images/centered-strokes.svg?raw=true "centered-strokes")
### 7. Shapes (such as rectangles) must have a **border radius of**
#### A. **2 pixels** if they are at least 8 pixels in size
![2px-border-radius](../../images/2px-border-radius.svg?raw=true "2px-border-radius")
![2px-border-radius](../images/2px-border-radius.svg?raw=true "2px-border-radius")
#### B. **1 pixel** if they are smaller than 8 pixels in size
![1px-border-radius](../../images/1px-border-radius.svg?raw=true "1px-border-radius")
![1px-border-radius](../images/1px-border-radius.svg?raw=true "1px-border-radius")
### 8. Distinct elements must have **2 pixels of spacing between each other**
![2px-element-spacing](../../images/2px-element-spacing.svg?raw=true '2px-element-spacing')
![2px-element-spacing](../images/2px-element-spacing.svg?raw=true '2px-element-spacing')
![2px-element-spacing-connected](../../images/2px-element-spacing-connected.svg?raw=true '2px-element-spacing-connected')
![2px-element-spacing-connected](../images/2px-element-spacing-connected.svg?raw=true '2px-element-spacing-connected')
![2px-element-spacing-abrupt-cut](../../images/2px-element-spacing-abrupt-cut.svg?raw=true '2px-element-spacing-abrupt-cut')
![2px-element-spacing-abrupt-cut](../images/2px-element-spacing-abrupt-cut.svg?raw=true '2px-element-spacing-abrupt-cut')
### 9. Icons should have a similar optical volume to `circle` and `square`.
![optical-volume-ideal](../../images/optical-volume-ideal.svg?raw=true "optical-volume-ideal")
![optical-volume-ideal](../images/optical-volume-ideal.svg?raw=true "optical-volume-ideal")
![optical-volume-low](../../images/optical-volume-low.svg?raw=true "optical-volume-low")
![optical-volume-low](../images/optical-volume-low.svg?raw=true "optical-volume-low")
![optical-volume-high](../../images/optical-volume-high.svg?raw=true "optical-volume-high")
![optical-volume-high](../images/optical-volume-high.svg?raw=true "optical-volume-high")
**Tip:** place your icon next to the circle or square icon and blur them both; your icon should not feel much darker than the base shape.
### 10. Icons should be visually centered by their center of gravity.
![visually-centered](../../images/visually-centered.svg?raw=true "visually-centered")
![visually-centered](../images/visually-centered.svg?raw=true "visually-centered")
![visually-centered-bad](../../images/visually-centered-bad.svg?raw=true "visually-centered-bad")
![visually-centered-bad](../images/visually-centered-bad.svg?raw=true "visually-centered-bad")
**Tip:** place your icon both above/below and next to the square or circle icon and check if it feels off center. Symmetrical icons should always be aligned to the center.
### 11. Icons should have similar visual density and level of detail.
![density-ideal](../../images/density-ideal.svg?raw=true "density-ideal")
![density-ideal](../images/density-ideal.svg?raw=true "density-ideal")
![density-high](../../images/density-high.svg?raw=true "density-high")
![density-high](../images/density-high.svg?raw=true "density-high")
**Tip:** try to make abstractions to dense elements. Blur your icon, and when blurred it should not feel overly dark.
### 12. Continuous curves should join smoothly.
![curvature-smooth](../../images/curvature-smooth.svg?raw=true "curvature-smooth")
![curvature-smooth](../images/curvature-smooth.svg?raw=true "curvature-smooth")
![curvature-uneven](../../images/curvature-uneven.svg?raw=true "curvature-uneven")
![curvature-uneven](../images/curvature-uneven.svg?raw=true "curvature-uneven")
**Tip:** make sure to use arcs or quadratic curves. When using cubic curves control points should have mirrored angles for smooth curves.
### 13. Icons should aim to be pixel perfect so that they will be sharp on low DPI displays.
![pixel-perfection-ideal](../../images/pixel-perfection-ideal.svg?raw=true "pixel-perfection-ideal")
![pixel-perfection-ideal](../images/pixel-perfection-ideal.svg?raw=true "pixel-perfection-ideal")
![pixel-perfection-bad](../../images/pixel-perfection-bad.svg?raw=true "pixel-perfection-bad")
![pixel-perfection-bad](../images/pixel-perfection-bad.svg?raw=true "pixel-perfection-bad")
**Tip:** whenever possible align elements and arc centers to the grid.

View File

@@ -24,7 +24,7 @@ The Illustrator template is created following guidelines from the [Icon Design G
5. Export the file with the export menu under: `Export > Export As..` then save the file as SVG. Select the following options in the SVG Options dialog:
![SVG export options in Illustrator](../../images/illustrator-svg-options.png?raw=true "Setting Page Size")
![SVG export options in Illustrator](../images/illustrator-svg-options.png?raw=true "Setting Page Size")
After that, double check that the [code conventions and SVG global attributes](icon-design-guide.md#code-conventions) are correct.

View File

@@ -1,7 +1,6 @@
---
aside: false
title: Contributing to Lucide
title: Contribution guide
description: Guidelines and instructions for contributing to the Lucide project.
---
<!--@include: ../CONTRIBUTING.md -->
<!--@include: ../../CONTRIBUTING.md -->

View File

@@ -4,7 +4,7 @@ title: Icon Design Guide
# Inkscape Setup Guide
This guide shows the steps to setup Inkscape for creating icons that conform to the Featherity design
This guide shows the steps to setup Inkscape for creating icons that conform to the Lucide design
guidelines.
## Setting up The Canvas
@@ -13,11 +13,11 @@ When opening a new document, Inkscape will create a canvas of a default size. T
1. Open the Document Properties dialog (File -> Document Properties).
2. On the “Page Size” tab, under “Custom Size” set the Units to `px` and set both Height and Width to 24.
![Setting Page Size](../../images/page-size.png?raw=true "Setting Page Size")
![Setting Page Size](../images/page-size.png?raw=true "Setting Page Size")
3. On the “Grid” tab, select `Rectangular Grid` and click “New Grid”.
![Setting Grid Properties](../../images/grid-1.png?raw=true "Setting Grid Properties")
![Setting Grid Properties](../images/grid-1.png?raw=true "Setting Grid Properties")
4. Set the Grid Units to `px` and set Spacing X and Spacing Y both to 1.
![Setting Grid Properties](../../images/grid-2.png?raw=true "Setting Grid Properties")
![Setting Grid Properties](../images/grid-2.png?raw=true "Setting Grid Properties")
5. Close the Document Properties dialog.
6. To center the canvas in the viewport, select View -> Zoom -> Drawing.
@@ -25,17 +25,17 @@ When opening a new document, Inkscape will create a canvas of a default size. T
1. Create a path or shape.
2. With the path selected, open the Stroke and Fill panel by pressing `Ctrl+Shift+F` on your keyboard.
![Stroke Style Properties](../../images/strokes.png?raw=true "Setting Grid Properties")
![Stroke Style Properties](../images/strokes.png?raw=true "Setting Grid Properties")
3. On the “Stroke Style” tab:
* Set Stroke Width to `2px`.
* Select the rounded join type.
* Select the rounded cap type.
4. If the shape is a rectangle, select the rectangle and in the top of the screen below the menu bar, set `Rx` and `Ry` to `2px`.
![Rectangle Radius Properties](../../images/corner-radius.png?raw=true "Rectangle Radius Properties")
![Rectangle Radius Properties](../images/corner-radius.png?raw=true "Rectangle Radius Properties")
## Saving A File
1. When ready to save the file, click Save As and select “Optimized SVG” as the file type.
![Save As](../../images/save-as.png?raw=true "Save as")
![Save As](../images/save-as.png?raw=true "Save as")
2. After clicking Save, to conform with the other icons in the package, set Pretty Printing to use spaces and set the indentation depth to 2.
![Optimize](../../images/optimize-settings.png?raw=true "Optimize")
![Optimize](../images/optimize-settings.png?raw=true "Optimize")

View File

@@ -1,8 +1,9 @@
---
title: Accessibility
description: Learn how to make your icons accessible to all users, including those with disabilities.
---
# 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.
@@ -22,7 +23,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
@@ -32,21 +33,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.
@@ -55,7 +56,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.
@@ -65,14 +66,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
@@ -94,7 +95,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:
@@ -109,7 +110,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
@@ -137,7 +139,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
@@ -220,3 +222,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>

View File

@@ -1,103 +0,0 @@
# 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-react";
```
## 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-react", // or
"lucide-preact", // or
"lucide-react-native", // or
"lucide-vue-next",
]
}
```
### Create a custom module declaration file
Only available for `lucide-react`, `lucide-preact`, `lucide-react-native`, `lucide-vue-next` package.
This will enable you to choose the import name style you want to use in your project.
::: code-group
```ts [React]
declare module "lucide-react" {
// Prefixed import names
export * from "lucide-react/dist/lucide-react.prefixed";
// or
// Suffixed import names
export * from "lucide-react/dist/lucide-react.suffixed";
}
```
```ts [Vue]
declare module "lucide-vue-next" {
// Prefixed import names
export * from "lucide-vue-next/dist/lucide-vue-next.prefixed";
// or
// Suffixed import names
export * from "lucide-vue-next/dist/lucide-vue-next.suffixed";
}
```
```ts [Preact]
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";
}
```
```ts [React Native]
declare module "lucide-react-native" {
// Prefixed import names
export * from "lucide-react-native/dist/lucide-react-native.prefixed";
// or
// Suffixed import names
export * from "lucide-react-native/dist/lucide-react-native.suffixed";
}
```
```ts [@lucide-icons]
declare module "@lucide/icons" {
// Prefixed import names
export * from "@lucide/icons/dist/lucide-icons.prefixed";
// or
// Suffixed import names
export * from "@lucide/icons/dist/lucide-icons.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 `[package-name].d.ts`.
### Import name styles
| Import Style | Available imports | Declaration file import |
| ------------- | --------------------------- | ----------------------- |
| Default | Home, HomeIcon, LucideHome | |
| Prefixed | LucideHome | [package].prefixed |
| Suffixed | HomeIcon | [package].suffixed |

View File

@@ -1,23 +0,0 @@
import { Star, StarHalf } from "lucide-react";
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

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

View File

@@ -1,14 +0,0 @@
.star-rating {
position: relative;
}
.stars {
display: flex;
gap: 4px;
}
.rating {
position: absolute;
top: 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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