mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-10 13:18:25 +00:00
Compare commits
182
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f94ac6b77 | ||
|
|
93dd84c0d0 | ||
|
|
feb9abc633 | ||
|
|
44438b8d8a | ||
|
|
a79b2d131d | ||
|
|
b998e2892b | ||
|
|
5da3ee357d | ||
|
|
6bbe5ddb07 | ||
|
|
4a26fa58e0 | ||
|
|
5d592a96aa | ||
|
|
bdf795a8e3 | ||
|
|
e4be16acd5 | ||
|
|
ea1242d384 | ||
|
|
7cf805a408 | ||
|
|
c2c28580b3 | ||
|
|
858b662775 | ||
|
|
c17f928577 | ||
|
|
d32ef89ef9 | ||
|
|
5fb5967884 | ||
|
|
1cb69f6f9c | ||
|
|
6d4501f4d3 | ||
|
|
ba642ebf42 | ||
|
|
2f7d975022 | ||
|
|
b115c04286 | ||
|
|
5d0f64034b | ||
|
|
8a225b9fa7 | ||
|
|
95989f4535 | ||
|
|
073ffe0448 | ||
|
|
30f2eda422 | ||
|
|
a68cc8d52f | ||
|
|
249fe3a120 | ||
|
|
07ed920096 | ||
|
|
cef2020c7a | ||
|
|
8970c2c4f9 | ||
|
|
26993aba5a | ||
|
|
a53bd66a03 | ||
|
|
3bcf71f7cf | ||
|
|
9e5f0607cd | ||
|
|
75955ec47b | ||
|
|
58fd3ccd59 | ||
|
|
22e6094bb5 | ||
|
|
ba95e4c988 | ||
|
|
e8db514793 | ||
|
|
ab000be04f | ||
|
|
a537cb6eb3 | ||
|
|
3859eb20fa | ||
|
|
2bfb9bb1ba | ||
|
|
94e4cb9d9d | ||
|
|
0196353e5c | ||
|
|
99d25bdee2 | ||
|
|
07a2c792ef | ||
|
|
93a1d72465 | ||
|
|
83ba4986c8 | ||
|
|
65271a11e7 | ||
|
|
90567c4ce0 | ||
|
|
4dc5b7ebae | ||
|
|
a87bc9b15b | ||
|
|
f96d09cf3a | ||
|
|
713b1fcd25 | ||
|
|
bca7e75a81 | ||
|
|
7f30808e30 | ||
|
|
6f56b8f1f6 | ||
|
|
b1a94838ac | ||
|
|
ecccc7b963 | ||
|
|
3ccaafa69b | ||
|
|
a744a40e23 | ||
|
|
20cc4c12b6 | ||
|
|
f1d4838196 | ||
|
|
bdb3684959 | ||
|
|
85b455c323 | ||
|
|
d72697aeab | ||
|
|
431af7618e | ||
|
|
7a3df42d5a | ||
|
|
6022cf9878 | ||
|
|
d771617ede | ||
|
|
c711d5990c | ||
|
|
dce50dd0c9 | ||
|
|
32703fc21a | ||
|
|
5c7c083e56 | ||
|
|
c646eea633 | ||
|
|
066f42db8b | ||
|
|
eaace8a81b | ||
|
|
2119ad0eb1 | ||
|
|
c5a83d26ff | ||
|
|
eb3c04d059 | ||
|
|
796dad298f | ||
|
|
3f4a337c5d | ||
|
|
47ad47f321 | ||
|
|
c967b3225c | ||
|
|
0516a35c56 | ||
|
|
48fb4d0079 | ||
|
|
672d891a6b | ||
|
|
d8a7841d33 | ||
|
|
716ae6d4e9 | ||
|
|
cd6ee554c8 | ||
|
|
be0956479b | ||
|
|
9f3692dee3 | ||
|
|
af82050077 | ||
|
|
50de1605e1 | ||
|
|
56b50bba8a | ||
|
|
bd4cdc5f39 | ||
|
|
fb617bfd6e | ||
|
|
50eacd724a | ||
|
|
06e26d6ff1 | ||
|
|
a79d711591 | ||
|
|
1bcad8fccb | ||
|
|
8d8b8744d1 | ||
|
|
23f9abc4ed | ||
|
|
bc0e5d1929 | ||
|
|
ce14971fa6 | ||
|
|
f68bec5ad1 | ||
|
|
1a60fd28ed | ||
|
|
c42aba062d | ||
|
|
33a44aa8b0 | ||
|
|
59978cecf8 | ||
|
|
2c9b468404 | ||
|
|
7c758075a3 | ||
|
|
15804aa9eb | ||
|
|
86eb89c794 | ||
|
|
a08ebd74dc | ||
|
|
51b9aa19de | ||
|
|
c0ea22e282 | ||
|
|
2e88f33494 | ||
|
|
c5b33f81f8 | ||
|
|
f9d8161e6a | ||
|
|
e110ccf92f | ||
|
|
75b55160aa | ||
|
|
7367c8f0fc | ||
|
|
a7c781bd43 | ||
|
|
62527757e2 | ||
|
|
b7b6ecf131 | ||
|
|
113a3b1a3b | ||
|
|
ea29af9ae8 | ||
|
|
249af14dc6 | ||
|
|
79fc531b30 | ||
|
|
232b1ea84b | ||
|
|
08e27e3387 | ||
|
|
0f8d48b266 | ||
|
|
07df1c7ca2 | ||
|
|
31f5872aa0 | ||
|
|
0f7ac1f988 | ||
|
|
f3a26ccff1 | ||
|
|
701be9088a | ||
|
|
8503b1d4b7 | ||
|
|
53766c77ec | ||
|
|
510f495687 | ||
|
|
c4b02a0c2e | ||
|
|
801d773d1f | ||
|
|
4993e7893f | ||
|
|
d958c44840 | ||
|
|
caa622b4ea | ||
|
|
1d7da164bb | ||
|
|
8294fabe0c | ||
|
|
c0b203a996 | ||
|
|
6d1fc51b9a | ||
|
|
1502285072 | ||
|
|
240118e521 | ||
|
|
4aec3f892f | ||
|
|
dbfe130652 | ||
|
|
d29db5e98e | ||
|
|
bb26580cfb | ||
|
|
74c46711c7 | ||
|
|
c3b6795394 | ||
|
|
33342b8790 | ||
|
|
9ecaafbc4d | ||
|
|
1d578ab536 | ||
|
|
c757cb9d4b | ||
|
|
5d36179bf4 | ||
|
|
357df0d660 | ||
|
|
5a06f7cb1d | ||
|
|
63d0ef6664 | ||
|
|
0adb963d07 | ||
|
|
89147bbecc | ||
|
|
0ad5aaf323 | ||
|
|
50f9af4ed2 | ||
|
|
4c61dc4826 | ||
|
|
99a8602c5d | ||
|
|
75b6ac1969 | ||
|
|
138ce8e748 | ||
|
|
87ec1a0587 | ||
|
|
02eaf471a9 | ||
|
|
3035ed309e |
@@ -9,3 +9,5 @@ strikethrough
|
||||
touchpad
|
||||
ungroup
|
||||
toc
|
||||
sunlounger
|
||||
monstera
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
lib
|
||||
tests
|
||||
node_modules
|
||||
.eslintrc.js
|
||||
docs/images
|
||||
docs/**/examples/
|
||||
packages/lucide-react/dynamicIconImports.js
|
||||
packages/angular/.angular
|
||||
@@ -1,75 +0,0 @@
|
||||
const DEFAULT_ATTRS = require('./tools/build-icons/render/default-attrs.json');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
extends: ['airbnb-base', 'prettier'],
|
||||
plugins: ['import', '@html-eslint'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: ['**/*.test.js', '**/*.spec.js', '**/scripts/**'],
|
||||
},
|
||||
],
|
||||
'import/extensions': [
|
||||
'error',
|
||||
{
|
||||
pattern: {
|
||||
mjs: 'always',
|
||||
json: 'always',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./docs/tsconfig.json', './packages/*/tsconfig.json'],
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ['./icons/*.svg'],
|
||||
parser: '@html-eslint/parser',
|
||||
rules: {
|
||||
'@html-eslint/require-doctype': 'off',
|
||||
'@html-eslint/no-duplicate-attrs': 'error',
|
||||
'@html-eslint/no-inline-styles': 'error',
|
||||
'@html-eslint/require-attrs': [
|
||||
'error',
|
||||
...Object.entries(DEFAULT_ATTRS).map(([attr, value]) => ({
|
||||
tag: 'svg',
|
||||
attr,
|
||||
value: String(value),
|
||||
})),
|
||||
],
|
||||
'@html-eslint/indent': ['error', 2],
|
||||
'@html-eslint/no-multiple-empty-lines': ['error', { max: 0 }],
|
||||
'@html-eslint/no-extra-spacing-attrs': [
|
||||
'error',
|
||||
{
|
||||
enforceBeforeSelfClose: true,
|
||||
},
|
||||
],
|
||||
'@html-eslint/require-closing-tags': [
|
||||
'error',
|
||||
{
|
||||
selfClosing: 'always',
|
||||
allowSelfClosingCustom: true,
|
||||
},
|
||||
],
|
||||
'@html-eslint/element-newline': 'error',
|
||||
'@html-eslint/no-trailing-spaces': 'error',
|
||||
'@html-eslint/quotes': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
title: 'Icon Request: '
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting an icon request check if it has already been requested. If there is an open request, please add a 👍.
|
||||
|
||||
> [!CAUTION]
|
||||
> New brand logos are **not** allowed, see our official statement: https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md.
|
||||
>
|
||||
> Existing brand icons have been phased out. Consider using https://simpleicons.org, which offers purpose-built SVGs that are also on a 24×24px grid.
|
||||
- type: input
|
||||
id: name
|
||||
attributes:
|
||||
label: Icon name
|
||||
description: What should this icon depict? For multiple icons, provide a comma-separated list.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: use-cases
|
||||
attributes:
|
||||
label: Use cases
|
||||
description: Why do you need this icon? Include at least two real-life use cases per requested icon, avoiding generic descriptions like "it's a car icon".
|
||||
placeholder: e.g. I need a star icon to use in my rating system.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: design-ideas
|
||||
attributes:
|
||||
label: Design ideas
|
||||
description: What should this icon look like? Provide simple, minimalistic icon examples from other sets or your own drafts to help us visualize your request.
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Please review the following checklist before submitting your request.
|
||||
options:
|
||||
- label: I have searched if someone has submitted a similar request before and there weren't any.
|
||||
required: true
|
||||
- label: I have searched existing icons to make sure it does not already exist and I didn't find any.
|
||||
required: true
|
||||
- label: I am not requesting a brand logo and the art is not protected by copyright, see more at https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md
|
||||
required: true
|
||||
- label: I am not requesting an icon that includes religious, war/violence related, political imagery or hate symbols.
|
||||
required: true
|
||||
- label: I have provided appropriate use cases for the icon(s) requested.
|
||||
required: true
|
||||
@@ -0,0 +1,106 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
groups:
|
||||
react-deps:
|
||||
patterns:
|
||||
- '@testing-library/react'
|
||||
- '@types/prop-types'
|
||||
- '@types/react'
|
||||
- '@vitejs/plugin-react'
|
||||
- 'react'
|
||||
- 'react-dom'
|
||||
vue-deps:
|
||||
patterns:
|
||||
- '@testing-library/vue'
|
||||
- '@vitejs/plugin-vue'
|
||||
- '@vue/test-utils'
|
||||
- 'vue'
|
||||
svelte-deps:
|
||||
patterns:
|
||||
- '@sveltejs/*'
|
||||
- '@testing-library/svelte'
|
||||
- '@tsconfig/svelte'
|
||||
- 'svelte'
|
||||
- 'svelte-check'
|
||||
- 'svelte-preprocess'
|
||||
solid-deps:
|
||||
patterns:
|
||||
- '@babel/*'
|
||||
- '@rollup/plugin-babel'
|
||||
- '@solidjs/testing-library'
|
||||
- 'babel-preset-solid'
|
||||
- 'solid-js'
|
||||
- 'vite-plugin-solid'
|
||||
react-native-deps:
|
||||
patterns:
|
||||
- 'react-native'
|
||||
- 'react-native-svg'
|
||||
angular-deps:
|
||||
patterns:
|
||||
- '@angular/*'
|
||||
- '@angular-eslint/*'
|
||||
- '@vitest/browser-playwright'
|
||||
- 'angular-eslint'
|
||||
- 'ng-packagr'
|
||||
- 'rxjs'
|
||||
- 'tslib'
|
||||
preact-deps:
|
||||
patterns:
|
||||
- '@preact/preset-vite'
|
||||
- '@testing-library/preact'
|
||||
- 'preact'
|
||||
astro-deps:
|
||||
patterns:
|
||||
- '@astrojs/*'
|
||||
- 'astro'
|
||||
testing-deps:
|
||||
patterns:
|
||||
- '@testing-library/*'
|
||||
- '@vitest/*'
|
||||
- 'jest-serializer-html'
|
||||
- 'jsdom'
|
||||
- 'linkedom'
|
||||
- 'vitest'
|
||||
build-deps:
|
||||
patterns:
|
||||
- '@rollup/*'
|
||||
- '@vitejs/*'
|
||||
- 'esbuild'
|
||||
- 'rollup'
|
||||
- 'rollup-plugin-*'
|
||||
- 'typescript'
|
||||
- 'vite'
|
||||
- 'vite-plugin-*'
|
||||
linting-deps:
|
||||
patterns:
|
||||
- '@eslint/*'
|
||||
- '@html-eslint/*'
|
||||
- '@typescript-eslint/*'
|
||||
- 'angular-eslint'
|
||||
- 'cspell'
|
||||
- 'eslint'
|
||||
- 'eslint-*'
|
||||
- 'husky'
|
||||
- 'lint-staged'
|
||||
- 'prettier'
|
||||
- 'prettier-plugin-*'
|
||||
types-deps:
|
||||
patterns:
|
||||
- '@types/*'
|
||||
remaining-deps:
|
||||
patterns:
|
||||
- '*'
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- '*'
|
||||
@@ -13,8 +13,72 @@ The `contributors` property is a required array of GitHub usernames for the peop
|
||||
|
||||
The `tags` property is an array of strings that describe the icon and can be used for searching.
|
||||
Validate the tags against the `icon.schema.json` to ensure they are correctly formatted and adhere to the defined structure.
|
||||
Provide tag suggestions based on the name of the icon and the use cases provided in the PR description. Use the existing tags in the repository as a reference for consistency and to avoid duplicates. Don't suggest words like: 'icon' and preferably use single words. Tags should always be in lowercase and should not contain spaces. The name of icon should not be included in the tags, as it is already specified.
|
||||
Provide tag suggestions based on the name of the icon and the use cases provided in the PR description. Use the existing tags in the repository as a reference for consistency and to avoid duplicates. Don't suggest words like: 'icon' and preferably use single words. Tags should always be in lowercase, and may also contain spaces (e.g. `magnifying glass`). The name of icon should not be included in the tags, as it is already specified.
|
||||
|
||||
# Categories
|
||||
## Categories
|
||||
|
||||
The `categories` property is an array of strings that specify the categories to which the icon belongs, such as "devices", "interface", "media", etc. See the `categories` property in the `icon.schema.json` for more details on the allowed values. The categories should be chosen based on the use cases provided in the PR description and the existing categories in the repository. The categories should be relevant to the icon and should help users find the icon when searching for specific types of icons. The name of icon should not be included in the categories, as it is already specified. Suggest categories based on the name of the icon and the use cases provided in the PR description. Use the existing categories in the repository as a reference for consistency and to avoid duplicates. Categories should always be in lowercase and should not contain spaces.
|
||||
|
||||
## Use Cases
|
||||
|
||||
The `use-cases` property is an array of strings that describe the specific scenarios in which the icon would be used. Each use case should be concise and clearly convey the context in which the icon is applicable. Use the existing use cases in the repository as a reference for consistency and to avoid duplicates.
|
||||
|
||||
### Writing icon use-cases
|
||||
|
||||
A use-case tells a reader **where and why an icon would be used in a real interface**. Each entry should let someone who has never seen the icon understand what it's for.
|
||||
|
||||
### Format
|
||||
|
||||
- Write each use-case as a short phrase, not a sentence. Start with a present participle verb: *Representing…*, *Indicating…*, *Marking…*, *Showing…*, *Confirming…*, *Toggling…*, *Categorizing…*, *Searching…*.
|
||||
- Keep it to roughly 4–12 words. One clear idea per entry.
|
||||
- No trailing period. No leading bullet, number, or dash (the surrounding format supplies that).
|
||||
- Give each icon 1–4 use-cases, ordered most-common first. Prefer fewer strong entries over many weak or repetitive ones.
|
||||
|
||||
### Voice
|
||||
|
||||
- Write from the interface's perspective, describing the function: "Indicating a low battery charge level" — not the requester's story ("I need an icon that shows…").
|
||||
- Name the concrete context where useful: *in smart home apps*, *in navigation apps*, *in text editors*, *on maps*. Context is what makes a use-case self-explanatory.
|
||||
- Be neutral and factual. No first person, no marketing adjectives ("perfect for", "ideal", "beautiful"), no emoji.
|
||||
|
||||
### What to exclude
|
||||
|
||||
- **The requester's personal narrative.** "I'm building a SvelteKit UI and need…" → drop the framing, keep the function.
|
||||
- **References to other issues, PRs, or icons by ID.** No "As provided in #1865", "complementary to git-branch-plus", "same as above", "see #2055".
|
||||
- **Design/implementation chatter.** Bezier offsets, grid alignment, path cuts, "the icon works perfectly" — none of that is a use-case.
|
||||
- **Restating the icon's name.** For `microchip`, "microchip" is not a use-case; describe what it represents ("Representing processors, chips, or embedded hardware").
|
||||
- **Links, image tags, emoji, and markdown artifacts.**
|
||||
- **Meta-commentary.** "Self-explanatory", "lots and lots of use cases", "obvious :)".
|
||||
|
||||
### Cross-contamination
|
||||
|
||||
When a request covers several icons at once, assign each use-case only to the icon it actually describes. A bullet prefixed with another icon's name belongs to that icon, not this one. Don't let `briefcase-medical`'s meanings leak into `briefcase-business`.
|
||||
|
||||
### Variants
|
||||
|
||||
Related icons should get **variant-specific** wording, not one shared blurb. `battery-low` → "Indicating a low battery charge level"; `battery-full` → "…full…". `square-arrow-right-enter` → sign in / join; `-exit` → sign out / export. Resist copying the same line across a family.
|
||||
|
||||
### Deriving from source material
|
||||
|
||||
When mining descriptions, comments, or linked issues:
|
||||
|
||||
- Prefer the **stated purpose** over the backstory. Extract the function, discard the anecdote.
|
||||
- If a source lists genuinely distinct uses, keep them as separate entries; if it repeats one idea five ways, collapse to one.
|
||||
- Merge an intro line with its bullets ("To represent:" + "pottery" → "Representing pottery").
|
||||
- If the only signal is noise (a name restatement, a link, "thanks!"), leave the icon without a use-case rather than inventing one.
|
||||
- Never fabricate a use-case that isn't supported by the icon's meaning or the source.
|
||||
|
||||
## Examples
|
||||
|
||||
Good:
|
||||
|
||||
- `ambulance` → "Directing users to nearby emergency services in navigation apps"
|
||||
- `calendar-sync` → "Representing recurring events, promotions, or limited-time offers"
|
||||
- `globe-x` → "Indicating a device or application is offline or unreachable"
|
||||
- `heading-2` → "Applying a level-2 heading in text editors"
|
||||
|
||||
Poor → fixed:
|
||||
|
||||
- ~~"I need a firewall icon :-)"~~ → "Representing firewalls and firewall rules on cybersecurity platforms"
|
||||
- ~~"As above"~~ → "Signifying a deal, agreement, or partnership"
|
||||
- ~~"microchip"~~ → "Representing processors, chips, or embedded hardware"
|
||||
- ~~"Same use cases as the other currency symbols, except Turkish lira"~~ → "Displaying prices or amounts in Turkish lira"
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
---
|
||||
applyTo: "docs/*.md"
|
||||
---
|
||||
# UX writing guidelines
|
||||
|
||||
Follow these guidelines when writing or editing Lucide documentation, especially Markdown files.
|
||||
|
||||
Write for people who are trying to understand a concept, follow a specification, or complete a task. Prioritize clarity, precision, and scannability over personality or decorative prose.
|
||||
|
||||
## Be direct and concise
|
||||
|
||||
State the important information first. Remove words that do not add meaning.
|
||||
|
||||
Prefer short sentences and paragraphs, but do not sacrifice precision just to make text shorter.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Icons must use a 24 × 24 pixel canvas.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> When creating a new icon, you should make sure that the canvas you're working with has dimensions of 24 × 24 pixels.
|
||||
|
||||
Avoid unnecessary introductions such as:
|
||||
|
||||
- "It's important to note that…"
|
||||
- "Keep in mind that…"
|
||||
- "As you can see…"
|
||||
- "In order to…"
|
||||
- "It should be noted that…"
|
||||
|
||||
State the information directly instead.
|
||||
|
||||
## Put the rule before the explanation
|
||||
|
||||
When documenting a requirement or recommendation, state it before explaining why it exists.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Keep at least 1 pixel of space between unrelated elements. This prevents shapes from visually merging at small sizes.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Because shapes can visually merge at small sizes, it's important to make sure that unrelated elements have enough space between them.
|
||||
|
||||
This makes documentation easier to scan and makes normative requirements easier to identify.
|
||||
|
||||
## Explain the reason when it adds value
|
||||
|
||||
Explain *why* a rule exists when the reasoning helps contributors apply it to situations that are not explicitly documented.
|
||||
|
||||
Do not merely restate the rule.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Avoid simulated fills. They don't scale consistently with the rest of the icon set.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Avoid simulated fills because simulated fills should not be used.
|
||||
|
||||
Reasoning is particularly useful for design rules where contributors may need to evaluate unusual cases.
|
||||
|
||||
## Use scannable structure
|
||||
|
||||
Assume readers will scan the page before reading it in detail.
|
||||
|
||||
Use:
|
||||
|
||||
- descriptive headings
|
||||
- short paragraphs
|
||||
- lists for sets of related items
|
||||
- examples for rules that benefit from demonstration
|
||||
- **bold** for important terms or short emphasis
|
||||
- code formatting for filenames, properties, commands, values, and identifiers
|
||||
|
||||
Avoid long uninterrupted blocks of text.
|
||||
|
||||
Headings should communicate the subject or rule without requiring the surrounding text for context.
|
||||
|
||||
**Do:**
|
||||
|
||||
> ## Keep 1 pixel of safe space
|
||||
|
||||
**Don't:**
|
||||
|
||||
> ## Spacing
|
||||
|
||||
## Use sentence case
|
||||
|
||||
Use sentence case for:
|
||||
|
||||
- page titles
|
||||
- headings
|
||||
- table headings
|
||||
- labels
|
||||
- example captions
|
||||
|
||||
Capitalize proper nouns and branded terms normally.
|
||||
|
||||
**Do:**
|
||||
|
||||
> ## Use consistent corner radii
|
||||
|
||||
**Don't:**
|
||||
|
||||
> ## Use Consistent Corner Radii
|
||||
|
||||
## Address the reader directly
|
||||
|
||||
Use **you** and **your** when referring to something the reader does.
|
||||
|
||||
**Do:**
|
||||
|
||||
> If your icon contains overlapping shapes, simplify the hidden geometry.
|
||||
|
||||
Avoid referring to the reader as "the user," "the contributor," or other third-person terms when direct language is more natural.
|
||||
|
||||
However, prefer an imperative when the subject is obvious.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> Keep related elements visually grouped.
|
||||
|
||||
**Over:**
|
||||
|
||||
> You should keep related elements visually grouped.
|
||||
|
||||
## Use "we" only when it means the Lucide project or maintainers
|
||||
|
||||
First-person plural is acceptable when describing an intentional project decision, convention, or policy.
|
||||
|
||||
**Do:**
|
||||
|
||||
> We use `kebab-case` for icon names.
|
||||
|
||||
> We generally avoid adding brand logos to Lucide.
|
||||
|
||||
Do not use "we" merely to make documentation conversational.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> Icons use a 2 pixel stroke.
|
||||
|
||||
**Over:**
|
||||
|
||||
> We use a 2 pixel stroke for our icons.
|
||||
|
||||
## Use normative language consistently
|
||||
|
||||
Use specific language to communicate how strictly a rule applies.
|
||||
|
||||
- **must** — required; violating this is not acceptable
|
||||
- **must not** — prohibited
|
||||
- **should** — recommended; exceptions may be appropriate
|
||||
- **should not** — discouraged; exceptions may be appropriate
|
||||
- **can** — possible or permitted
|
||||
- **may** — optional or dependent on context
|
||||
|
||||
Do not use **should** when something is actually required.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Icons must use a 24 × 24 pixel canvas.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Icons should use a 24 × 24 pixel canvas.
|
||||
|
||||
Avoid vague substitutes such as "ideally," "normally," or "preferably" when a more precise normative term applies.
|
||||
|
||||
## Prefer active voice
|
||||
|
||||
Use active voice unless passive voice makes the subject clearer or avoids unnecessary repetition.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Align nodes to the pixel grid.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Nodes should be aligned to the pixel grid.
|
||||
|
||||
Passive voice is acceptable when the actor is irrelevant.
|
||||
|
||||
> Deprecated icons are removed in major releases.
|
||||
|
||||
## Use positive instructions when possible
|
||||
|
||||
Tell readers what to do rather than only what not to do.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Use a single continuous path when the elements form one continuous shape.
|
||||
|
||||
**Less useful:**
|
||||
|
||||
> Don't split continuous shapes into unnecessary paths.
|
||||
|
||||
Use negative instructions when the prohibition itself is important.
|
||||
|
||||
> Don't use simulated fills.
|
||||
|
||||
## Be precise about consequences
|
||||
|
||||
When explaining why something should or should not be done, describe the actual consequence rather than using vague warnings.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Keep unrelated elements at least 2 pixels apart. Smaller gaps can disappear when the icon is rendered at small sizes.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Be careful when placing elements too close together.
|
||||
|
||||
Avoid language that exaggerates consequences or attempts to influence a contributor's decision emotionally.
|
||||
|
||||
## Prefer concrete language
|
||||
|
||||
Use specific terms instead of vague references.
|
||||
|
||||
**Do:**
|
||||
|
||||
> The two endpoints must remain inside the 1 pixel safe zone.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> These should stay within the appropriate area.
|
||||
|
||||
Avoid ambiguous references such as:
|
||||
|
||||
- this
|
||||
- that
|
||||
- it
|
||||
- things
|
||||
- stuff
|
||||
- appropriate
|
||||
- proper
|
||||
- correct
|
||||
|
||||
when the intended subject can be named directly.
|
||||
|
||||
## Introduce terminology before relying on it
|
||||
|
||||
Define Lucide-specific or technical terms when readers may not already know them.
|
||||
|
||||
Once defined, use the same term consistently.
|
||||
|
||||
**Do:**
|
||||
|
||||
> The **safe zone** is the 1 pixel area between the icon artwork and the canvas boundary.
|
||||
|
||||
Do not alternate between different terms for the same concept unless the distinction is intentional.
|
||||
|
||||
For example, don't alternate between "safe zone," "padding," and "margin" if they refer to the same rule.
|
||||
|
||||
## Use abbreviations sparingly
|
||||
|
||||
Spell out uncommon abbreviations on first use.
|
||||
|
||||
> Scalable Vector Graphics (SVG)
|
||||
|
||||
Common technical abbreviations such as HTML, CSS, SVG, API, and URL do not need to be expanded when the intended audience can reasonably be expected to know them.
|
||||
|
||||
Avoid Latin abbreviations such as:
|
||||
|
||||
- e.g.
|
||||
- i.e.
|
||||
- etc.
|
||||
|
||||
Prefer:
|
||||
|
||||
- for example
|
||||
- that is
|
||||
- and so on
|
||||
|
||||
## Use examples deliberately
|
||||
|
||||
Use examples when they clarify how to apply a rule.
|
||||
|
||||
Keep examples focused on the rule being demonstrated. Avoid introducing unrelated differences that make the comparison harder to understand.
|
||||
|
||||
When contrasting examples, use **Do** and **Don't** consistently.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Use one path for a continuous outline.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Split a continuous outline into several overlapping paths.
|
||||
|
||||
The explanatory text should describe the principle, not merely narrate what is visible in the example.
|
||||
|
||||
## Use links descriptively
|
||||
|
||||
Link text should describe the destination or concept.
|
||||
|
||||
**Do:**
|
||||
|
||||
> See the [Icon Design Guide](./design-principles) for exact dimensions and spacing requirements.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> For more information, [click here](./design-principles).
|
||||
|
||||
Avoid generic link text such as:
|
||||
|
||||
- click here
|
||||
- here
|
||||
- this page
|
||||
- learn more
|
||||
|
||||
when a descriptive label is possible.
|
||||
|
||||
## Avoid unnecessary repetition
|
||||
|
||||
Do not repeat information already established by a heading, example, or immediately preceding sentence.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> ## Keep 1 pixel of safe space
|
||||
>
|
||||
> Keep all artwork within the 22 × 22 pixel live area.
|
||||
|
||||
**Over:**
|
||||
|
||||
> ## Keep 1 pixel of safe space
|
||||
>
|
||||
> When designing an icon, you should keep 1 pixel of safe space around the icon.
|
||||
|
||||
Cross-reference another section instead of duplicating detailed rules that already have a canonical explanation.
|
||||
|
||||
## Use contractions in prose
|
||||
|
||||
Use natural contractions such as:
|
||||
|
||||
- don't
|
||||
- can't
|
||||
- isn't
|
||||
- it's
|
||||
- you'll
|
||||
|
||||
They make explanatory prose less formal and easier to scan.
|
||||
|
||||
Use uncontracted forms when deliberate emphasis is useful.
|
||||
|
||||
> Do not change existing icon geometry solely to reduce the number of SVG nodes.
|
||||
|
||||
Normative statements using **must not** should not be contracted.
|
||||
|
||||
## Use the serial comma
|
||||
|
||||
Use the serial (Oxford) comma in lists of three or more items.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Check the icon's size, stroke width, and spacing.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> Check the icon's size, stroke width and spacing.
|
||||
|
||||
## Use punctuation normally in prose
|
||||
|
||||
Use periods for complete sentences.
|
||||
|
||||
Periods may be omitted from:
|
||||
|
||||
- headings
|
||||
- short labels
|
||||
- short list items that are not complete sentences
|
||||
- captions consisting only of a short phrase
|
||||
|
||||
Be consistent within a list.
|
||||
|
||||
If list items are complete sentences, end them with periods. If they are fragments, generally omit periods.
|
||||
|
||||
## Skip colons in headings
|
||||
|
||||
Do not end headings with colons.
|
||||
|
||||
**Do:**
|
||||
|
||||
> ## Naming conventions
|
||||
|
||||
**Don't:**
|
||||
|
||||
> ## Naming conventions:
|
||||
|
||||
Use a colon normally when introducing a list or explanation in body text.
|
||||
|
||||
## Use exclamation points sparingly
|
||||
|
||||
Avoid exclamation points in technical documentation unless the context genuinely calls for excitement or celebration.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> Your first icon is ready.
|
||||
|
||||
**Over:**
|
||||
|
||||
> Your first icon is ready!
|
||||
|
||||
Do not use exclamation points to make instructions sound friendlier.
|
||||
|
||||
## Use ellipses only when they have meaning
|
||||
|
||||
Use an ellipsis (`…`) to represent omitted content or an intentionally incomplete statement.
|
||||
|
||||
Do not use ellipses decoratively or to make prose sound conversational.
|
||||
|
||||
When documenting literal UI text, preserve the punctuation used by the interface.
|
||||
|
||||
## Use parentheses for definitions and compact clarification
|
||||
|
||||
Use parentheses when they make a definition, abbreviation, or short clarification easier to understand.
|
||||
|
||||
> Scalable Vector Graphics (SVG)
|
||||
|
||||
Avoid using parentheses for long side notes. Rewrite important information as part of the sentence or as a separate sentence.
|
||||
|
||||
## Prefer "and" over ampersands
|
||||
|
||||
Use **and** in normal prose.
|
||||
|
||||
**Do:**
|
||||
|
||||
> Naming and metadata
|
||||
|
||||
**Avoid:**
|
||||
|
||||
> Naming & metadata
|
||||
|
||||
Use an ampersand only when it is part of an official name, literal UI text, code, or another established term.
|
||||
|
||||
## Avoid em dashes
|
||||
|
||||
Prefer commas, periods, colons, or parentheses when they produce a simpler sentence.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> This rule has one exception: existing legacy icons.
|
||||
|
||||
**Avoid:**
|
||||
|
||||
> This rule has one exception — existing legacy icons.
|
||||
|
||||
Use an en dash (`–`) without surrounding spaces for ranges.
|
||||
|
||||
> 1–3 pixels
|
||||
|
||||
> A–Z
|
||||
|
||||
Do not substitute a hyphen for an en dash in prose ranges unless required by a technical syntax.
|
||||
|
||||
## Use hyphens correctly
|
||||
|
||||
Hyphenate compound modifiers when necessary for clarity.
|
||||
|
||||
> 24 × 24 pixel canvas
|
||||
|
||||
> 2-pixel-wide gap
|
||||
|
||||
> pixel-aligned element
|
||||
|
||||
Do not hyphenate a phrase when it functions as a verb.
|
||||
|
||||
> Sign off on the design.
|
||||
|
||||
> Complete the sign-off.
|
||||
|
||||
Do not add hyphens mechanically when the meaning is already clear.
|
||||
|
||||
## Use code formatting consistently
|
||||
|
||||
Use backticks for literal technical values and identifiers, including:
|
||||
|
||||
- filenames and paths
|
||||
- commands
|
||||
- package names
|
||||
- properties and attributes
|
||||
- icon names
|
||||
- code values
|
||||
- SVG elements and attributes
|
||||
|
||||
**Do:**
|
||||
|
||||
> Add `camera-off` as an alias.
|
||||
|
||||
> Set `stroke-width` to `2`.
|
||||
|
||||
> Edit `icons/camera.svg`.
|
||||
|
||||
Do not use code formatting merely for emphasis.
|
||||
|
||||
## Use bold for semantic emphasis
|
||||
|
||||
Use **bold** sparingly to highlight a term or distinction that helps readers scan the page.
|
||||
|
||||
Do not bold entire sentences or paragraphs.
|
||||
|
||||
Prefer bold over italics for general emphasis.
|
||||
|
||||
Use *italics* only when conventional, such as when referring to a term as a term or when a specific distinction benefits from it.
|
||||
|
||||
## Don't use all caps for emphasis
|
||||
|
||||
Do not use blocks of uppercase text for emphasis.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> NEVER place nodes outside the canvas.
|
||||
|
||||
**Do:**
|
||||
|
||||
> **Never** place nodes outside the canvas.
|
||||
|
||||
Preserve capitalization when documenting literal identifiers or established technical terms.
|
||||
|
||||
## Keep lists parallel
|
||||
|
||||
Items in the same list should use the same grammatical structure.
|
||||
|
||||
**Do:**
|
||||
|
||||
- Use a 24 × 24 pixel canvas
|
||||
- Keep 1 pixel of safe space
|
||||
- Use a 2 pixel stroke
|
||||
- Align nodes to the pixel grid
|
||||
|
||||
**Don't:**
|
||||
|
||||
- Use a 24 × 24 pixel canvas
|
||||
- Safe space should be 1 pixel
|
||||
- A 2 pixel stroke
|
||||
- Nodes are aligned to the pixel grid
|
||||
|
||||
Parallel structure makes rules easier to scan and compare.
|
||||
|
||||
## Avoid unnecessary filler and enthusiasm
|
||||
|
||||
Lucide documentation should sound helpful and confident, not promotional.
|
||||
|
||||
Avoid filler such as:
|
||||
|
||||
- "Simply…"
|
||||
- "Just…"
|
||||
- "Obviously…"
|
||||
- "Of course…"
|
||||
- "Easy…"
|
||||
- "Luckily…"
|
||||
- "Don't worry…"
|
||||
|
||||
These words can be inaccurate or dismissive when the task is not simple for the reader.
|
||||
|
||||
Also avoid marketing language such as:
|
||||
|
||||
- "powerful"
|
||||
- "seamless"
|
||||
- "effortless"
|
||||
- "amazing"
|
||||
- "best-in-class"
|
||||
|
||||
unless the word communicates a specific, verifiable distinction.
|
||||
|
||||
## Prefer timeless documentation
|
||||
|
||||
Avoid wording tied unnecessarily to the current state of development.
|
||||
|
||||
**Prefer:**
|
||||
|
||||
> Lucide supports custom icon aliases.
|
||||
|
||||
**Avoid:**
|
||||
|
||||
> Lucide now supports custom icon aliases.
|
||||
|
||||
Use time-sensitive language only when the timing itself matters, such as migration notes, deprecations, or release documentation.
|
||||
|
||||
## Preserve established Lucide terminology
|
||||
|
||||
When editing existing documentation, preserve established Lucide terminology and naming conventions unless there is a clear reason to change them.
|
||||
|
||||
Do not invent synonyms merely to avoid repetition. Consistency is more important than stylistic variety in technical documentation.
|
||||
|
||||
When terminology differs between the Lucide codebase, API, UI, and documentation, prefer the term used by the relevant canonical specification.
|
||||
@@ -30,6 +30,11 @@
|
||||
- any-glob-to-any-file:
|
||||
- 'icons/*.svg'
|
||||
|
||||
🔬 Lab icon:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'lab/*.svg'
|
||||
|
||||
# For changes in the lucide package
|
||||
🧳 lucide package:
|
||||
- changed-files:
|
||||
|
||||
@@ -47,7 +47,9 @@ Common scopes: icons, docs, studio, site, dev
|
||||
|
||||
### Design <!-- ONLY for new icons -->
|
||||
<!-- All of these requirements must be fulfilled. -->
|
||||
|
||||
- [ ] I've read and followed the [icon design guidelines](https://lucide.dev/contribute/icon-design-guide)
|
||||
- [ ] I've made sure that the icons don't [already exist](https://lucide.dev/icons), including the [Lab directory](https://github.com/lucide-icons/lucide/tree/main/lab).
|
||||
- [ ] I've made sure that the icons look sharp on low DPI displays.
|
||||
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
|
||||
- [ ] I've made sure that the icons are visually centered.
|
||||
|
||||
@@ -7,13 +7,16 @@ on:
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -27,9 +30,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
+46
-13
@@ -23,13 +23,7 @@ on:
|
||||
- beta
|
||||
- alpha
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
# This is added to make sure we wait until last release is done, this is needed for the font-build process
|
||||
concurrency:
|
||||
group: continuous-integration
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-dispatch-gate:
|
||||
@@ -37,10 +31,12 @@ jobs:
|
||||
startsWith(github.event.head_commit.message, 'feat(icons)') &&
|
||||
github.event_name != 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
@@ -64,12 +60,15 @@ jobs:
|
||||
needs: check-dispatch-gate
|
||||
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to create the release and tag
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
RELEASE_URL: ${{ steps.create-release.outputs.url }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -104,7 +103,8 @@ jobs:
|
||||
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
id: create-release
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
@@ -115,13 +115,46 @@ jobs:
|
||||
needs: create-release
|
||||
uses: './.github/workflows/release.yml'
|
||||
secrets: inherit
|
||||
permissions:
|
||||
id-token: write # Required for OIDC (npm provenance)
|
||||
contents: write # Required to upload release assets
|
||||
with:
|
||||
version: ${{ needs.create-release.outputs.VERSION }}
|
||||
|
||||
dispatch-release:
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
if: github.repository == 'lucide-icons/lucide' && github.event_name == 'workflow_dispatch'
|
||||
uses: './.github/workflows/release.yml'
|
||||
secrets: inherit
|
||||
permissions:
|
||||
id-token: write # Required for OIDC (npm provenance)
|
||||
contents: write # Required to upload release assets
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
tag: ${{ inputs.tag }}
|
||||
|
||||
post-release:
|
||||
name: Trigger post-release automation
|
||||
if: >-
|
||||
always() &&
|
||||
github.repository == 'lucide-icons/lucide' &&
|
||||
(needs.start-release.result == 'success' || needs.dispatch-release.result == 'success')
|
||||
needs: [create-release, start-release, dispatch-release]
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Dispatch post_release event
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.POST_RELEASE_TOKEN }}
|
||||
RELEASE_TAG: ${{ needs.create-release.outputs.VERSION || inputs.version }}
|
||||
RELEASE_URL: ${{ needs.create-release.outputs.RELEASE_URL }}
|
||||
run: |
|
||||
if [ -z "$RELEASE_URL" ]; then
|
||||
RELEASE_URL="https://github.com/lucide-icons/lucide/releases/tag/$RELEASE_TAG"
|
||||
fi
|
||||
|
||||
gh api \
|
||||
--method POST \
|
||||
repos/lucide-icons/post-release/dispatches \
|
||||
--field event_type=post_release \
|
||||
--field "client_payload[release_tag]=$RELEASE_TAG" \
|
||||
--field "client_payload[release_url]=$RELEASE_URL"
|
||||
|
||||
@@ -3,18 +3,28 @@ name: Close Icon Requests with Brand Terms
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
discussion:
|
||||
types: [created]
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
jobs:
|
||||
block_phrases:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
discussions: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Load stopwords from JSON & check issue title & body
|
||||
if: contains(github.event.issue.labels.*.name, '🙌 icon request')
|
||||
if: github.event_name == 'issues' && contains(github.event.issue.labels.*.name, '🙌 icon request')
|
||||
run: |
|
||||
ISSUE_TITLE=$(jq -r '.issue.title' "$GITHUB_EVENT_PATH")
|
||||
ISSUE_BODY=$(jq -r '.issue.body // ""' "$GITHUB_EVENT_PATH")
|
||||
@@ -27,6 +37,8 @@ jobs:
|
||||
if echo "$ISSUE_TITLE" | grep -iqE "$SAFE_KEY|$SAFE_VALUE" || \
|
||||
{ [ -n "$ICON_NAME_SECTION" ] && echo "$ICON_NAME_SECTION" | grep -iqE "$SAFE_KEY|$SAFE_VALUE"; }; then
|
||||
|
||||
URL_ENCODED_VALUE=$(jq -rn --arg val "$VALUE" '$val|@uri')
|
||||
|
||||
gh issue close ${{ github.event.issue.number }} \
|
||||
--reason "not planned" \
|
||||
--comment "It looks like this request is about **${VALUE}**, which is a brand logo.
|
||||
@@ -35,7 +47,7 @@ jobs:
|
||||
|
||||
[Click here to read our official statement about brand logos in Lucide.](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${URL_ENCODED_VALUE})
|
||||
|
||||
We’re always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
|
||||
@@ -44,5 +56,55 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Load stopwords from JSON & check discussion title & body
|
||||
if: github.event_name == 'discussion' && github.event.discussion.category.slug == 'icon-design'
|
||||
run: |
|
||||
DISCUSSION_TITLE=$(jq -r '.discussion.title' "$GITHUB_EVENT_PATH")
|
||||
DISCUSSION_BODY=$(jq -r '.discussion.body // ""' "$GITHUB_EVENT_PATH")
|
||||
ICON_NAME_SECTION=$(printf '%s\n' "$DISCUSSION_BODY" | awk '/### Icon name/{flag=1; next} /^### /{flag=0} flag')
|
||||
|
||||
jq -r 'to_entries[] | "\(.key) \(.value)"' brand-stopwords.json | while read -r KEY VALUE; do
|
||||
SAFE_KEY=$(printf '%s\n' "$KEY" | sed 's/[][\.^$*]/\\&/g')
|
||||
SAFE_VALUE=$(printf '%s\n' "$VALUE" | sed 's/[][\.^$*]/\\&/g')
|
||||
|
||||
if echo "$DISCUSSION_TITLE" | grep -iqE "$SAFE_KEY|$SAFE_VALUE" || \
|
||||
{ [ -n "$ICON_NAME_SECTION" ] && echo "$ICON_NAME_SECTION" | grep -iqE "$SAFE_KEY|$SAFE_VALUE"; }; then
|
||||
|
||||
URL_ENCODED_VALUE=$(jq -rn --arg val "$VALUE" '$val|@uri')
|
||||
DISCUSSION_NODE_ID=$(jq -r '.discussion.node_id' "$GITHUB_EVENT_PATH")
|
||||
COMMENT_BODY="It looks like this request is about **${VALUE}**, which is a brand logo.
|
||||
|
||||
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**.
|
||||
|
||||
[Click here to read our official statement about brand logos in Lucide.](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar discussions.](https://github.com/lucide-icons/lucide/discussions/categories/icon-design?discussions_q=category%253A%22Icon+design%22+${URL_ENCODED_VALUE})
|
||||
|
||||
We’re always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
|
||||
# Add comment to discussion
|
||||
gh api graphql -f query='
|
||||
mutation($discussionId: ID!, $body: String!) {
|
||||
addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
|
||||
comment { id }
|
||||
}
|
||||
}' -f discussionId="$DISCUSSION_NODE_ID" -f body="$COMMENT_BODY"
|
||||
|
||||
# Close discussion
|
||||
gh api graphql -f query='
|
||||
mutation($discussionId: ID!) {
|
||||
closeDiscussion(input: {discussionId: $discussionId}) {
|
||||
discussion { closed }
|
||||
}
|
||||
}' -f discussionId="$DISCUSSION_NODE_ID"
|
||||
|
||||
# Lock discussion
|
||||
gh api graphql -f query='
|
||||
mutation($lockableId: ID!, $reason: LockReason) {
|
||||
lockLockable(input: {lockableId: $lockableId, lockReason: $reason}) {
|
||||
lockedRecord { locked }
|
||||
}
|
||||
}' -f lockableId="$DISCUSSION_NODE_ID" -f reason="SPAM"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -3,13 +3,16 @@ on:
|
||||
schedule:
|
||||
- cron: '45 1 * * *'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-pr-message: This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.
|
||||
|
||||
@@ -6,15 +6,22 @@ on:
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read # Required to download the artifact from the triggering run
|
||||
pull-requests: write
|
||||
if: >
|
||||
github.repository == 'lucide-icons/lucide' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- name: 'Download artifact'
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -42,7 +49,7 @@ jobs:
|
||||
id: pr-number
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@v2
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: pr-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||
@@ -50,7 +57,7 @@ jobs:
|
||||
body-includes: Added or changed icons
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||
|
||||
@@ -9,13 +9,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -29,9 +32,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -2,12 +2,15 @@ name: 'Pull Request Labeler'
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/labeler@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
name: Linting PR
|
||||
name: Linting PR code
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- icons/*.svg
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint-code:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Linting PR
|
||||
name: Linting PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -7,12 +7,16 @@ on:
|
||||
- edited
|
||||
- reopened
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read # Required to read the PR title
|
||||
|
||||
jobs:
|
||||
lint-pr-title:
|
||||
name: PR Title Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -7,6 +7,9 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'icons/*'
|
||||
- 'lab/*'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
lint-filenames:
|
||||
@@ -16,27 +19,54 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: icons/*
|
||||
files: |
|
||||
icons/*
|
||||
lab/*
|
||||
|
||||
- name: Generate annotations
|
||||
run: node ./scripts/lintFilenames.mts
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
check-lab-icons:
|
||||
name: Check Lab Icons
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check lab icons do not already exist in icons
|
||||
run: node ./scripts/checkLabIcons.mts
|
||||
|
||||
lint-aliases:
|
||||
name: Check Uniqueness of Aliases
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Check Uniqueness of Aliases
|
||||
run: "! cat <(printf \"%s\\n\" icons/*.json | while read -r name; do basename \"$name\" .json; done) <(jq -cr 'select(.aliases) | .aliases[] | if type==\"string\" then . else .name end' icons/*.json) | sort | uniq -c | grep -ve '^\\s*1 '"
|
||||
|
||||
@@ -11,13 +11,16 @@ on:
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
@@ -31,9 +34,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -9,13 +9,16 @@ on:
|
||||
- tools/build-font/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-font:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -30,7 +33,7 @@ jobs:
|
||||
run: pnpm build:font
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Lucide Lab checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- lab/**
|
||||
- packages/lab/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @lucide/lab build
|
||||
@@ -12,13 +12,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-preact:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,13 +12,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,9 +35,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -7,19 +7,25 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-react/**
|
||||
- integrations/lucide-react/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- scripts/generateNextJSAliases.mjs
|
||||
- package.json
|
||||
- pnpm-workspace.yaml
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -33,9 +39,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -45,3 +51,22 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Chromium
|
||||
run: pnpm --filter @lucide/integration-react-vite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run integration checks
|
||||
run: pnpm test:integrations:react
|
||||
|
||||
@@ -9,13 +9,16 @@ on:
|
||||
- packages/shared/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,13 +12,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,9 +35,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -7,13 +7,16 @@ on:
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-static:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,13 +12,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,9 +35,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -48,9 +51,9 @@ jobs:
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -6,19 +6,22 @@ on:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue/**
|
||||
- packages/vue/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,9 +35,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -11,13 +11,16 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -31,9 +34,9 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -5,27 +5,30 @@ on:
|
||||
paths:
|
||||
- 'icons/*.svg'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
generate-changed-icons-comment:
|
||||
name: Generate Changed Icons Comment
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v46
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: icons/*.svg
|
||||
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- name: Install svgson for code preview (safer and faster than installing all deps)
|
||||
run: npm install svgson@5.3.1 --force
|
||||
|
||||
@@ -40,7 +43,7 @@ jobs:
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: pr_number
|
||||
path: pr/
|
||||
|
||||
@@ -5,9 +5,12 @@ on:
|
||||
paths:
|
||||
- 'icons/*.json'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pull-request-metadata-suggestions:
|
||||
name: Pull Request Metadata Suggestions
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -16,11 +19,11 @@ jobs:
|
||||
steps:
|
||||
# We checkout the main branch of main repository for security reasons.
|
||||
# This is to prevent the workflow from running on a forked repository.
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
repository: lucide-icons/lucide
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
name: Release @lucide/lab
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- lab/**/*.svg
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-type:
|
||||
description: Release type
|
||||
required: true
|
||||
default: 'minor'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
create-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to create the release and tag
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
SHOULD_RELEASE: ${{ steps.release-notes.outputs.SHOULD_RELEASE }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0 # Required to diff the lab icons against the previous tag
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Get latest tag
|
||||
id: latest-tag
|
||||
run: |
|
||||
LATEST_TAG=$(git tag --list '@lucide/lab@*' --sort=-v:refname | head -n 1)
|
||||
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "::error::No @lucide/lab@* tag found to release from."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Latest tag: $LATEST_TAG"
|
||||
echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create new version
|
||||
id: new-version
|
||||
run: |
|
||||
NEW_VERSION=$(pnpm semver "${LATEST_TAG#@lucide/lab@}" -i "$RELEASE_TYPE")
|
||||
|
||||
echo "New version: $NEW_VERSION"
|
||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
RELEASE_TYPE: ${{ inputs.release-type || 'minor' }}
|
||||
|
||||
- name: Generate release notes
|
||||
id: release-notes
|
||||
run: |
|
||||
NEW_TAG="@lucide/lab@$NEW_VERSION"
|
||||
|
||||
ADDED=$(git diff --name-only --diff-filter=A "$LATEST_TAG" HEAD -- 'lab/*.svg' \
|
||||
| xargs -r -n1 basename | sed 's/\.svg$//' | sort)
|
||||
REMOVED=$(git diff --name-only --diff-filter=D "$LATEST_TAG" HEAD -- 'lab/*.svg' \
|
||||
| xargs -r -n1 basename | sed 's/\.svg$//' | sort)
|
||||
|
||||
if [ -z "$ADDED" ] && [ "$EVENT_NAME" != 'workflow_dispatch' ]; then
|
||||
echo "::notice::No icons were added to ./lab since $LATEST_TAG, skipping release."
|
||||
echo "SHOULD_RELEASE=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
{
|
||||
if [ -n "$ADDED" ]; then
|
||||
printf '## New icons\n\n'
|
||||
printf '%s\n' "$ADDED" | sed 's|.*|- `&`|'
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if [ -n "$REMOVED" ]; then
|
||||
printf '## Removed icons\n\nThese graduated to the core icon set.\n\n'
|
||||
printf '%s\n' "$REMOVED" | sed 's|.*|- `&`|'
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
printf '**Full changelog**: https://github.com/lucide-icons/lucide/compare/%s...%s\n' "$LATEST_TAG" "$NEW_TAG"
|
||||
} > lab-release-notes.md
|
||||
|
||||
cat lab-release-notes.md
|
||||
echo "SHOULD_RELEASE=true" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
|
||||
NEW_VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
|
||||
- name: Create Release
|
||||
if: steps.release-notes.outputs.SHOULD_RELEASE == 'true'
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: '@lucide/lab@${{ steps.new-version.outputs.NEW_VERSION }}'
|
||||
name: '@lucide/lab@${{ steps.new-version.outputs.NEW_VERSION }}'
|
||||
body_path: lab-release-notes.md
|
||||
make_latest: 'false' # Never displace the core icon release as "Latest"
|
||||
|
||||
publish:
|
||||
if: github.repository == 'lucide-icons/lucide' && needs.create-release.outputs.SHOULD_RELEASE == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
needs: create-release
|
||||
permissions:
|
||||
id-token: write # Required for OIDC (npm provenance)
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm version ${{ needs.create-release.outputs.VERSION }} --filter @lucide/lab --fail-if-no-match --no-git-tag-version
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @lucide/lab build
|
||||
|
||||
- name: Publish
|
||||
run: pnpm --filter @lucide/lab publish --access public --no-git-checks --ignore-scripts --tag latest
|
||||
env:
|
||||
NPM_CONFIG_PROVENANCE: true
|
||||
@@ -16,9 +16,7 @@ on:
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
@@ -26,7 +24,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
steps:
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
@@ -60,9 +57,9 @@ jobs:
|
||||
'@lucide/vue',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -93,10 +90,14 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -122,10 +123,12 @@ jobs:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -142,7 +145,7 @@ jobs:
|
||||
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
@@ -152,18 +155,21 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare, lucide-font]
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
contents: write # Required to upload the release assets
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
- name: Zip font and icons
|
||||
run: |
|
||||
zip -r lucide-font-${{ needs.prepare.outputs.VERSION }}.zip lucide-font
|
||||
zip -r lucide-icons-${{ needs.prepare.outputs.VERSION }}.zip icons
|
||||
|
||||
- name: Release zip and fonts
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.VERSION }}
|
||||
|
||||
|
||||
@@ -4,14 +4,17 @@ on:
|
||||
# Run this workflow every Monday at 07:00 UTC
|
||||
schedule:
|
||||
- cron: '0 7 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
weekly-summary:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
name: 'Request Review'
|
||||
name: Request Review
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
paths:
|
||||
- icons/*.svg
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
request-review:
|
||||
if: github.event.pull_request.draft == false
|
||||
if: github.repository == 'lucide-icons/lucide' && github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: refs/pull/${{ github.event.pull_request.number }}/merge
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@v41
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
with:
|
||||
files: icons/*.svg
|
||||
- run: |
|
||||
|
||||
+12
-2
@@ -3,9 +3,10 @@
|
||||
.next
|
||||
.obsidian
|
||||
.now
|
||||
.idea
|
||||
.cache
|
||||
.env
|
||||
node_modules
|
||||
.pnpm-store
|
||||
dist
|
||||
build
|
||||
sandbox
|
||||
@@ -13,6 +14,10 @@ stash
|
||||
coverage
|
||||
stats
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
integrations/**/.react-router
|
||||
integrations/**/.vitest-attachments
|
||||
integrations/**/__screenshots__
|
||||
outlined
|
||||
lucide-font
|
||||
packages/**/src/icons/*.js
|
||||
@@ -23,6 +28,9 @@ packages/**/src/aliases.ts
|
||||
!packages/**/src/aliases/index.ts
|
||||
packages/**/src/utils/*.ts
|
||||
!packages/shared/src/utils/*.ts
|
||||
!packages/shared/src/build/
|
||||
!packages/shared/src/build/*.ts
|
||||
packages/shared/src/build/types.d.ts
|
||||
packages/**/src/dynamicIconImports.ts
|
||||
packages/**/DynamicIcon.d.ts
|
||||
packages/**/dynamicIconImports.js
|
||||
@@ -54,8 +62,10 @@ docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vitepress/data/brandStopwords.json
|
||||
docs/.vitepress/data/iconPopularity
|
||||
docs/.vitepress/data/lab/iconNodes
|
||||
docs/.vitepress/data/lab/iconMetaData.ts
|
||||
docs/.vitepress/data/lab/iconPopularity
|
||||
docs/.vercel
|
||||
docs/.nitro
|
||||
docs/public/og/**
|
||||
!docs/public/og/general.png
|
||||
.gitignore
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint-staged
|
||||
pnpm checkIcons
|
||||
|
||||
@@ -19,6 +19,13 @@ packages/lucide-static/icon-nodes.json
|
||||
packages/lucide-static/font
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
# integration test artifacts and generated routes
|
||||
integrations/**/.react-router
|
||||
integrations/**/next-env.d.ts
|
||||
integrations/**/routeTree.gen.ts
|
||||
integrations/**/.vitest-attachments
|
||||
integrations/**/__screenshots__
|
||||
|
||||
# lucide-svelte
|
||||
packages/svelte/src/icons/*.svelte
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"cSpell.words": ["devs", "linecap", "linejoin", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "editor"
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ Read it here: [ICON_GUIDELINES](https://lucide.dev/contribute/icon-design-guide)
|
||||
|
||||
#### Lucide Studio
|
||||
|
||||
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
|
||||
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/?utm_source=lucide.dev&utm_medium=contributing-guide). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
|
||||
|
||||
#### Editor guides
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@
|
||||
"kubernetes": "Kubernetes",
|
||||
"leetcode": "LeetCode",
|
||||
"leet-code": "LeetCode",
|
||||
"line": "LINE",
|
||||
"linkedin": "LinkedIn",
|
||||
"lua": "Lua",
|
||||
"mariadb": "MariaDB",
|
||||
@@ -69,7 +68,7 @@
|
||||
"nestjs": "NestJS",
|
||||
"netflix": "Netflix",
|
||||
"netlify": "Netlify",
|
||||
"next": "Next.js",
|
||||
"nextjs": "Next.js",
|
||||
"nodejs": "Node.js",
|
||||
"notion": "Notion",
|
||||
"nostr": "Nostr",
|
||||
@@ -105,7 +104,8 @@
|
||||
"shopify": "Shopify",
|
||||
"skype": "Skype",
|
||||
"slack": "Slack",
|
||||
"solid": "SolidJS",
|
||||
"snapchat": "Snapchat",
|
||||
"solidjs": "SolidJS",
|
||||
"soundcloud": "SoundCloud",
|
||||
"spotify": "Spotify",
|
||||
"sqlite": "SQLite",
|
||||
@@ -144,6 +144,5 @@
|
||||
"xbox": "Xbox",
|
||||
"yarn": "Yarn",
|
||||
"youtube": "YouTube",
|
||||
"zig": "Zig",
|
||||
"zoom": "Zoom"
|
||||
"zig": "Zig"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Emoji",
|
||||
"icon": "smile"
|
||||
"icon": "face-slightly-smiling"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../../data/lab/iconMetaData';
|
||||
import iconPopularity from '../../../data/lab/iconPopularity';
|
||||
import iconNodes from '../../../data/lab/iconNodes';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(iconMetaData).map(([iconName, iconData]) => {
|
||||
// Remove $schema from the response
|
||||
delete iconData['$schema'];
|
||||
|
||||
return [
|
||||
iconName,
|
||||
{
|
||||
name: iconName,
|
||||
iconNode: iconNodes[iconName],
|
||||
popularity: iconPopularity[iconName]?.count ?? 0,
|
||||
...iconData,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { eventHandler, setResponseHeader } from 'h3';
|
||||
import iconMetaData from '../../../data/lab/iconMetaData';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
for (const iconDetail in iconMetaData) {
|
||||
// Remove $schema from the response
|
||||
delete iconMetaData[iconDetail]['$schema'];
|
||||
}
|
||||
|
||||
return iconMetaData;
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { eventHandler, getQuery, setResponseHeader } from 'h3';
|
||||
import iconNodes from '../../../data/lab/iconNodes';
|
||||
type IconNodeWithKeys = [elementName: string, attrs: Record<string, string> & { key?: string }][];
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const query = getQuery(event);
|
||||
|
||||
const withUniqueKeys = query.withUniqueKeys === 'true';
|
||||
|
||||
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
|
||||
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
|
||||
|
||||
if (withUniqueKeys) {
|
||||
return iconNodes;
|
||||
}
|
||||
|
||||
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
|
||||
if (withUniqueKeys) {
|
||||
return [name, iconNode];
|
||||
}
|
||||
|
||||
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
|
||||
return [name, attrs];
|
||||
});
|
||||
|
||||
acc[name] = newIconNode;
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-i
|
||||
import sidebar from './sidebar';
|
||||
import snackPlayer from './markdown/snackPlayer';
|
||||
import sandpackPlugin from './markdown/sandpack';
|
||||
import examplePlugin from './markdown/example';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resourcesSidebar } from './sidebar/resources';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
@@ -31,6 +32,7 @@ export default defineConfig({
|
||||
config(md) {
|
||||
md.use(groupIconMdPlugin);
|
||||
md.use(snackPlayer);
|
||||
md.use(examplePlugin);
|
||||
md.use(sandpackPlugin, {
|
||||
defaultFiles: {
|
||||
'/styles.css': {
|
||||
@@ -96,7 +98,8 @@ export default defineConfig({
|
||||
text: 'Resources',
|
||||
items: [
|
||||
...resourcesSidebar[0].items,
|
||||
{ text: 'Design icons', link: '/contribute/icon-design-guide' },
|
||||
{ text: 'How to use icons', link: '/how-to/' },
|
||||
{ text: 'Contributing icons', link: '/contribute/icons/' },
|
||||
],
|
||||
},
|
||||
{ text: 'Packages', link: '/packages' },
|
||||
|
||||
@@ -46,21 +46,5 @@
|
||||
"light": "/company-logos/open-collective-light.svg",
|
||||
"dark": "/company-logos/open-collective-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Super",
|
||||
"url": "https://super.so",
|
||||
"image": {
|
||||
"light": "/company-logos/super-light.svg",
|
||||
"dark": "/company-logos/super-dark.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Noodle",
|
||||
"url": "https://noodle.run/",
|
||||
"image": {
|
||||
"light": "/company-logos/noodle-light.svg",
|
||||
"dark": "/company-logos/noodle-dark.svg"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
interface BackdropProps {
|
||||
src: string;
|
||||
|
||||
@@ -3,7 +3,6 @@ import pathToPoints from './path-to-points';
|
||||
import { Path, PathProps } from './types';
|
||||
|
||||
export const GapViolationHighlight = ({
|
||||
radius,
|
||||
stroke,
|
||||
strokeWidth,
|
||||
strokeOpacity,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import { PathProps, Path } from './types';
|
||||
import getPaths, { assert } from './utils';
|
||||
import { GapViolationHighlight } from './GapViolationHighlight.tsx';
|
||||
@@ -293,7 +293,7 @@ const Radii = ({
|
||||
...props
|
||||
}: { paths: Path[] } & PathProps<
|
||||
'strokeWidth' | 'stroke' | 'strokeDasharray' | 'strokeOpacity',
|
||||
any
|
||||
never
|
||||
>) => {
|
||||
return (
|
||||
<g
|
||||
@@ -361,12 +361,12 @@ const Radii = ({
|
||||
const Handles = ({
|
||||
paths,
|
||||
...props
|
||||
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', any>) => (
|
||||
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', never>) => (
|
||||
<g
|
||||
className="svg-preview-handles-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ c, prev, next, cp1, cp2 }, i) => (
|
||||
{paths.map(({ prev, next, cp1, cp2 }, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||
{cp1 && (
|
||||
|
||||
@@ -12,7 +12,9 @@ function pathToPoints({ d, prev, next }: Path, interval = 1) {
|
||||
points.push(commander.getPointAtLength(i));
|
||||
}
|
||||
points.push(next);
|
||||
} catch (err) {}
|
||||
} catch (err) {
|
||||
console.warn(`Failed to convert path to points for d="${d}": ${err}`);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export const getCommands = (src: string) =>
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d)
|
||||
.toAbs()
|
||||
// @ts-ignore
|
||||
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
|
||||
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
@@ -242,7 +242,7 @@ const getPaths = (src: string) => {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error every command type is handled above, so `c` is not `never` here
|
||||
assertNever(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ const getIconCodes = (): CodeExampleType => {
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
<script>
|
||||
import { createIcons, $CamelCase } from 'lucide';
|
||||
import { createIcons, $PascalCase } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
$CamelCase
|
||||
$PascalCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -119,8 +119,7 @@ const highlighter = await createHighlighter({
|
||||
});
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
ThemeRegistration | { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
export const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlightedCode = highlighter
|
||||
|
||||
@@ -2,8 +2,7 @@ import { bundledLanguages, type ThemeRegistration } from 'shiki';
|
||||
import { createHighlighter } from 'shiki';
|
||||
|
||||
export type ThemeOptions =
|
||||
| ThemeRegistration
|
||||
| { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
ThemeRegistration | { light: ThemeRegistration; dark: ThemeRegistration };
|
||||
|
||||
const highLightCode = async (code: string, lang: string, active?: boolean) => {
|
||||
const highlighter = await createHighlighter({
|
||||
|
||||
@@ -9,11 +9,9 @@ export const hash = (string: string, seed = 5381) => {
|
||||
let i = string.length;
|
||||
|
||||
while (i) {
|
||||
// eslint-disable-next-line no-bitwise, no-plusplus
|
||||
seed = (seed * 33) ^ string.charCodeAt(--i);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-bitwise
|
||||
return (seed >>> 0).toString(36).substr(0, 6);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, isAbsolute, resolve } from 'node:path';
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
|
||||
import container from 'markdown-it-container';
|
||||
|
||||
type ContainerArgs = [typeof container, string, { render: RenderRule }];
|
||||
type GuidanceType = 'do' | 'dont' | 'caution';
|
||||
|
||||
const isGuidanceOpenToken = (type: string) =>
|
||||
type === 'container_do_open' ||
|
||||
type === 'container_dont_open' ||
|
||||
type === 'container_caution_open';
|
||||
|
||||
const isGuidanceCloseToken = (type: string) =>
|
||||
type === 'container_do_close' ||
|
||||
type === 'container_dont_close' ||
|
||||
type === 'container_caution_close';
|
||||
|
||||
const parseInfo = (info: string, marker: string) => info.trim().slice(marker.length).trim();
|
||||
|
||||
const stripWrappingQuotes = (value: string) => {
|
||||
const quote = value[0];
|
||||
|
||||
if ((quote === '"' || quote === "'") && value.at(-1) === quote) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const isRawSvgUrl = (url: string) => {
|
||||
const [filePath, query = ''] = url.split('?');
|
||||
const params = new URLSearchParams(query);
|
||||
|
||||
return filePath.endsWith('.svg') && params.get('raw') === 'true';
|
||||
};
|
||||
|
||||
const isImageUrl = (value: string) => {
|
||||
const [filePath] = value.split('?');
|
||||
|
||||
return /\.(avif|gif|jpe?g|png|svg|webp)$/i.test(filePath);
|
||||
};
|
||||
|
||||
const resolveMarkdownPath = (env: Record<string, unknown>) =>
|
||||
(env.path as string | undefined) ??
|
||||
(env.file as string | undefined) ??
|
||||
(env.realPath as string | undefined);
|
||||
|
||||
const renderImage = (
|
||||
url: string,
|
||||
env: Record<string, unknown>,
|
||||
escapeAttr: (value: string) => string,
|
||||
) => {
|
||||
if (!isRawSvgUrl(url)) {
|
||||
return `<img class="example-guidance__image" src="${escapeAttr(url)}" alt="" loading="lazy">`;
|
||||
}
|
||||
|
||||
const [filePath] = url.split('?');
|
||||
const markdownPath = resolveMarkdownPath(env);
|
||||
const resolvedPath = isAbsolute(filePath)
|
||||
? resolve(process.cwd(), `.${filePath}`)
|
||||
: resolve(markdownPath ? dirname(markdownPath) : process.cwd(), filePath);
|
||||
const svg = readFileSync(resolvedPath, 'utf8');
|
||||
const replacedSvg = svg
|
||||
.replaceAll(/(#000(000)?|black)/g, 'currentColor')
|
||||
.replaceAll(' stroke="#D8D8D9"', 'style="stroke: currentColor; stroke-opacity: .25;"');
|
||||
|
||||
return `<div class="example-guidance__image example-guidance__image--raw">${replacedSvg}</div>`;
|
||||
};
|
||||
|
||||
const renderPreview = (
|
||||
value: string,
|
||||
env: Record<string, unknown>,
|
||||
escapeHtml: (value: string) => string,
|
||||
renderInline: (value: string) => string,
|
||||
) => {
|
||||
if (isImageUrl(value)) {
|
||||
return renderImage(value, env, escapeHtml);
|
||||
}
|
||||
|
||||
return `<div class="example-guidance__text">${renderInline(value)}</div>`;
|
||||
};
|
||||
|
||||
const hasTopLevelContent = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
let guidanceDepth = 0;
|
||||
|
||||
for (let i = index + 1; i < tokens.length; i += 1) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type === 'container_example_close') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
guidanceDepth += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isGuidanceCloseToken(token.type)) {
|
||||
guidanceDepth -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (guidanceDepth === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const hasGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
for (let i = index + 1; i < tokens.length; i += 1) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type === 'container_example_close') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isFirstGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
for (let i = index - 1; i >= 0; i -= 1) {
|
||||
const token = tokens[i];
|
||||
|
||||
if (token.type === 'container_example_open') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isGuidanceOpenToken(token.type)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const getExampleOpenIndex = (tokens: Parameters<RenderRule>[0], index: number) => {
|
||||
for (let i = index; i >= 0; i -= 1) {
|
||||
if (tokens[i].type === 'container_example_open') {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
};
|
||||
|
||||
const labels: Record<GuidanceType, string> = {
|
||||
do: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`,
|
||||
dont: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`,
|
||||
caution: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-exclamation-point"><path d="M12 20 L12 20" /><path d="M12 4 L12 15" /></svg> Caution`,
|
||||
};
|
||||
|
||||
export default function examplePlugin(md: MarkdownIt) {
|
||||
const escapeHtml = md.utils.escapeHtml;
|
||||
|
||||
const createExampleContainer = (): ContainerArgs => [
|
||||
container,
|
||||
'example',
|
||||
{
|
||||
render(tokens, idx) {
|
||||
const token = tokens[idx];
|
||||
|
||||
if (token.nesting === 1) {
|
||||
const hasContent = hasTopLevelContent(tokens, idx);
|
||||
const guidanceOnlyClass = hasContent ? '' : ' example-block--guidance-only';
|
||||
const contentOpen = hasContent ? '<div class="example-block__content">\n' : '';
|
||||
|
||||
return `<section class="example-block${guidanceOnlyClass}">\n${contentOpen}`;
|
||||
}
|
||||
|
||||
const exampleOpenIndex = getExampleOpenIndex(tokens, idx);
|
||||
const contentClose =
|
||||
hasTopLevelContent(tokens, exampleOpenIndex) && !hasGuidance(tokens, exampleOpenIndex)
|
||||
? '</div>\n'
|
||||
: '';
|
||||
|
||||
return `${contentClose}</section>\n`;
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const createGuidanceContainer = (type: GuidanceType): ContainerArgs => [
|
||||
container,
|
||||
type,
|
||||
{
|
||||
render(tokens, idx, _options, env) {
|
||||
const token = tokens[idx];
|
||||
|
||||
if (token.nesting === 1) {
|
||||
const exampleOpenIndex = getExampleOpenIndex(tokens, idx);
|
||||
const preview = stripWrappingQuotes(parseInfo(token.info, type));
|
||||
const media = renderPreview(preview, env, escapeHtml, md.renderInline.bind(md));
|
||||
const contentClose =
|
||||
hasTopLevelContent(tokens, exampleOpenIndex) && isFirstGuidance(tokens, idx)
|
||||
? '</div>\n'
|
||||
: '';
|
||||
const label = labels[type];
|
||||
|
||||
return `${contentClose}<figure class="example-guidance example-guidance--${type}">${media}<figcaption class="example-guidance__caption"><div class="example-guidance__label">${label}</div>\n`;
|
||||
}
|
||||
|
||||
return '</figcaption></figure>\n';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
md.use(...createExampleContainer());
|
||||
md.use(...createGuidanceContainer('do'));
|
||||
md.use(...createGuidanceContainer('dont'));
|
||||
md.use(...createGuidanceContainer('caution'));
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type MarkdownIt from 'markdown-it';
|
||||
import type { RenderRule } from 'markdown-it/lib/renderer.mjs';
|
||||
import type Token from 'markdown-it/lib/token.mjs';
|
||||
import container from 'markdown-it-container';
|
||||
import sandpackTheme from '../theme/sandpackTheme.json';
|
||||
|
||||
@@ -21,14 +22,10 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
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) => {
|
||||
const renderSandbox = (tokenList: Token[], index: number) => {
|
||||
const renderFunc = (tokens: Token[], idx: number) => {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
const fileAttr: string[] = [];
|
||||
const attrs = Object.fromEntries(tokens[idx].attrs || []);
|
||||
|
||||
const files: Record<
|
||||
@@ -47,7 +44,7 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
) {
|
||||
if (tokens[i].type === 'fence' && tokens[i].tag === 'code') {
|
||||
const info = tokens[i].info ?? '';
|
||||
const [lang, fileName, params = ''] = info.split(' ');
|
||||
const [, fileName, params = ''] = info.split(' ');
|
||||
|
||||
const active = params.includes('[active]');
|
||||
const hidden = params.includes('[hidden]');
|
||||
@@ -57,15 +54,9 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
if (fileName && code) {
|
||||
files[fileName] = {
|
||||
code,
|
||||
...(active && { active: true }),
|
||||
...(hidden && { hidden: true }),
|
||||
};
|
||||
|
||||
if (active) {
|
||||
(files[fileName] as any).active = true;
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
(files[fileName] as any).hidden = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +113,7 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
return renderFunc(tokenList, index);
|
||||
};
|
||||
|
||||
function createCodeGroup(md: MarkdownIt): ContainerArgs {
|
||||
function createCodeGroup(): ContainerArgs {
|
||||
return [
|
||||
container,
|
||||
'sandpack',
|
||||
@@ -134,5 +125,5 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
];
|
||||
}
|
||||
|
||||
md.use(...createCodeGroup(md));
|
||||
md.use(...createCodeGroup());
|
||||
}
|
||||
|
||||
@@ -73,7 +73,6 @@ export default function snackPlayerPlugin(md: MarkdownIt) {
|
||||
'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';
|
||||
@@ -88,7 +87,7 @@ export default function snackPlayerPlugin(md: MarkdownIt) {
|
||||
` data-snack-dependencies="${escapeHtml(dependencies)}"` +
|
||||
` data-snack-platform="${escapeHtml(platform)}"` +
|
||||
` data-snack-supported-platforms="${escapeHtml(supportedPlatforms)}"` +
|
||||
// ` data-snack-theme="${escapeHtml(theme)}"` +
|
||||
// ` data-snack-theme="${escapeHtml(params.theme ?? 'light')}"` +
|
||||
` data-snack-preview="${escapeHtml(preview)}"` +
|
||||
` data-snack-loading="${escapeHtml(loading)}"` +
|
||||
` data-snack-device-appearance="${escapeHtml(deviceAppearance)}"` +
|
||||
|
||||
@@ -94,7 +94,7 @@ export const angularSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ export const astroSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -36,6 +36,7 @@ const sidebar: Sidebar = {
|
||||
'/guide/react-native/': reactNativeSidebar,
|
||||
'/guide/angular/': angularSidebar,
|
||||
'/guide/static/': lucideStaticSidebar,
|
||||
'/how-to': resourcesSidebar,
|
||||
'/community': resourcesSidebar,
|
||||
'/contribute': resourcesSidebar,
|
||||
'/license': resourcesSidebar,
|
||||
|
||||
@@ -79,7 +79,7 @@ export const lucideSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -89,7 +89,7 @@ export const preactSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ export const reactSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,41 +11,105 @@ export const resourcesSidebar = [
|
||||
link: '/community',
|
||||
},
|
||||
{
|
||||
text: 'Code of Conduct',
|
||||
text: 'Code of conduct',
|
||||
link: '/code-of-conduct',
|
||||
},
|
||||
{
|
||||
text: 'Brand logo statement',
|
||||
link: '/brand-logo-statement',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contribute',
|
||||
items: [
|
||||
{
|
||||
text: 'Contributing',
|
||||
link: '/contribute',
|
||||
text: 'Contribution guide',
|
||||
link: '/contribute/',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Designing icons',
|
||||
text: 'How to use Lucide icons',
|
||||
items: [
|
||||
{
|
||||
text: 'Icon Design Principles',
|
||||
link: '/contribute/icon-design-guide',
|
||||
text: 'Getting started',
|
||||
link: '/how-to/',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Illustrator',
|
||||
link: '/contribute/illustrator-guide',
|
||||
text: 'Accessibility guide',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Learn how to make icons readable, understandable, and usable with assistive technology.',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Inkscape',
|
||||
link: '/contribute/inkscape-guide',
|
||||
text: 'How to pick the right icon',
|
||||
link: '/how-to/icon-guide',
|
||||
desc: 'Learn how to decide when icons belong in an interface and how to pick symbols people are likely to recognize.',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing icons',
|
||||
items: [
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/contribute/icons/',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Figma',
|
||||
link: '/contribute/figma-guide',
|
||||
text: 'Design language',
|
||||
link: '/contribute/icons/design-principles',
|
||||
desc: 'Learn the visual principles that shape Lucide icons with side-by-side examples.',
|
||||
},
|
||||
{
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/contribute/affinity-designer-guide',
|
||||
text: 'Naming conventions',
|
||||
link: '/contribute/icons/naming-conventions',
|
||||
desc: 'Learn how to choose clear, consistent, and predictable names for Lucide icons.',
|
||||
},
|
||||
{
|
||||
text: 'Metadata conventions',
|
||||
link: '/contribute/icons/metadata-conventions',
|
||||
desc: 'Learn how to write use cases, tags, categories, aliases, and other icon metadata.',
|
||||
items: [
|
||||
{
|
||||
text: 'Use case guide',
|
||||
link: '/contribute/icons/metadata-conventions/use-case-guide',
|
||||
desc: '',
|
||||
},
|
||||
{
|
||||
text: 'Tag guide',
|
||||
link: '/contribute/icons/metadata-conventions/tag-guide',
|
||||
desc: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'SVG conventions',
|
||||
link: '/contribute/icons/code-conventions',
|
||||
desc: 'Learn how to structure and write Lucide SVG files.',
|
||||
},
|
||||
{
|
||||
text: 'Design software guides',
|
||||
collapsed: false,
|
||||
desc: 'Learn how to design and export Lucide icons with common vector tools.',
|
||||
items: [
|
||||
{
|
||||
text: 'Adobe Illustrator',
|
||||
link: '/contribute/icons/illustrator-guide',
|
||||
desc: '',
|
||||
},
|
||||
{
|
||||
text: 'Inkscape',
|
||||
link: '/contribute/icons/inkscape-guide',
|
||||
},
|
||||
{
|
||||
text: 'Figma',
|
||||
link: '/contribute/icons/figma-guide',
|
||||
},
|
||||
{
|
||||
text: 'Affinity Designer',
|
||||
link: '/contribute/icons/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -90,7 +90,7 @@ export const solidSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ export const lucideStaticSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ export const svelteSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ export const vueSidebar = [
|
||||
items: [
|
||||
{
|
||||
text: 'Accessibility in depth',
|
||||
link: '/guide/accessibility',
|
||||
link: '/how-to/accessibility',
|
||||
desc: 'Accessibility best practices',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ const pickIcons = [
|
||||
'clapperboard',
|
||||
'tool-case',
|
||||
'printer',
|
||||
'album',
|
||||
'square-bookmark',
|
||||
'pencil-ruler',
|
||||
'calendar',
|
||||
'brain',
|
||||
@@ -53,7 +53,7 @@ const pickIcons = [
|
||||
'terminal',
|
||||
'thermometer',
|
||||
'thumbs-up',
|
||||
'trash-2',
|
||||
'trash',
|
||||
'umbrella',
|
||||
'watch',
|
||||
'sparkles',
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getAllData } from '../../../lib/icons';
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ const base64InnerSvg = computed(() => {
|
||||
</g>
|
||||
</svg>
|
||||
<a
|
||||
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}`"
|
||||
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=icon-detail-preview`"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="studio-button"
|
||||
|
||||
@@ -122,7 +122,7 @@ const categoriesList = computed(() => {
|
||||
});
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && (tags.value == null || categoriesMap.value == null),
|
||||
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
|
||||
);
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
|
||||
|
||||
@@ -88,9 +88,13 @@ const mappedIcons = computed(() => {
|
||||
return sortedIcons.value;
|
||||
}
|
||||
|
||||
if (categories.value == null) {
|
||||
return sortedIcons.value;
|
||||
}
|
||||
|
||||
return sortedIcons.value.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
const iconCategories = categories.value[icon.name] ?? [];
|
||||
|
||||
return {
|
||||
...icon,
|
||||
@@ -115,7 +119,7 @@ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && (tags.value == null || categories.value == null),
|
||||
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
|
||||
);
|
||||
|
||||
const chunkedIcons = computed(() => {
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
color: rgb(var(--text-color) / 1);
|
||||
display: none;
|
||||
top: -85%;
|
||||
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
background-image:
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
radial-gradient(circle, transparent 20%, var(--confetti-color) 20%, transparent 30%),
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
@@ -56,7 +57,8 @@
|
||||
.confetti-button:after {
|
||||
display: none;
|
||||
bottom: -75%;
|
||||
background-image: radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
background-image:
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
radial-gradient(circle, transparent 10%, var(--confetti-color) 15%, transparent 20%),
|
||||
radial-gradient(circle, var(--confetti-color) 20%, transparent 20%),
|
||||
|
||||
@@ -16,7 +16,7 @@ export const externalLibContext = {
|
||||
};
|
||||
|
||||
const externalLibIconNodesAPI = {
|
||||
lab: 'https://lab.lucide.dev/api/icon-details',
|
||||
lab: `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/lab/icon-details`,
|
||||
};
|
||||
|
||||
export function useExternalLibs(): ExternalLibContext {
|
||||
@@ -32,13 +32,15 @@ export function useExternalLibs(): ExternalLibContext {
|
||||
newExternalIconNodes[lib] = savedIconNodes[lib];
|
||||
} else {
|
||||
const response = await fetch(externalLibIconNodesAPI[lib]);
|
||||
const iconNodes = await response.json();
|
||||
const iconDetails = await response.json();
|
||||
|
||||
if (iconNodes) {
|
||||
newExternalIconNodes[lib] = Object.values(iconNodes).map((iconEntity: IconEntity) => ({
|
||||
...iconEntity,
|
||||
externalLibrary: lib,
|
||||
}));
|
||||
if (iconDetails) {
|
||||
newExternalIconNodes[lib] = Object.values(iconDetails).map((iconEntity: IconEntity) => {
|
||||
return {
|
||||
...iconEntity,
|
||||
externalLibrary: lib,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { ref, inject, Ref } from 'vue';
|
||||
|
||||
export const ICON_STYLE_CONTEXT = Symbol('style');
|
||||
|
||||
@@ -9,6 +9,7 @@ const useSearch = <T>(
|
||||
const index = shallowRef(
|
||||
new Fuse(collection.value, {
|
||||
threshold: 0.2,
|
||||
useExtendedSearch: true,
|
||||
keys,
|
||||
}),
|
||||
);
|
||||
@@ -17,7 +18,9 @@ const useSearch = <T>(
|
||||
index.value.setCollection(collection.value);
|
||||
|
||||
if (query.value) {
|
||||
return index.value.search(query.value).map((result) => result.item);
|
||||
return index.value
|
||||
.search({ $and: query.value.split(' ').filter((t) => !!t) })
|
||||
.map((result) => result.item);
|
||||
}
|
||||
|
||||
return collection.value;
|
||||
|
||||
@@ -183,3 +183,144 @@ html {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.custom-block.success {
|
||||
border-color: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
background-color: var(--vp-c-success-soft);
|
||||
}
|
||||
/* Example guidance blocks */
|
||||
.example-block {
|
||||
margin: 32px 0;
|
||||
}
|
||||
|
||||
.example-block__title {
|
||||
margin: 0 0 12px;
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.example-block > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.example-block > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.example-block__content > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.example-block__content > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.example-guidance__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border-bottom: 4px solid var(--vp-c-divider);
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.example-guidance__image--raw svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.example-guidance__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 160px;
|
||||
padding: 24px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 20px;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.example-guidance__text svg.lucide {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
vertical-align: -6px;
|
||||
}
|
||||
|
||||
.example-guidance__caption {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.example-guidance__caption > p {
|
||||
margin: 0;
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.example-guidance__label {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
margin: 0 0 16px;
|
||||
background-color: var(--example-guidance-label-bg);
|
||||
color: var(--vp-c-neutral-inverse);
|
||||
border-bottom-left-radius: 16px;
|
||||
border-bottom-right-radius: 16px;
|
||||
}
|
||||
|
||||
.example-guidance--do {
|
||||
--example-guidance-label-bg: var(--vp-c-success-1);
|
||||
}
|
||||
|
||||
.example-guidance--dont {
|
||||
--example-guidance-label-bg: var(--vp-c-danger-1);
|
||||
}
|
||||
|
||||
.example-guidance--caution {
|
||||
--example-guidance-label-bg: var(--vp-c-warning-1);
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.example-block {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.example-block__content {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.example-guidance {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.example-block {
|
||||
grid-template-columns: minmax(0, 0.9fr) repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.example-block--guidance-only {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.example-block__content {
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
.example-guidance {
|
||||
grid-column: auto;
|
||||
grid-row: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function useSearchPlaceholder(
|
||||
}
|
||||
}
|
||||
state.value = {
|
||||
isNoResults: query in BRAND_STOPWORDS && searchResults.length === 0 && query !== '',
|
||||
isNoResults: query !== '' && searchResults.length === 0,
|
||||
isBrand: query in BRAND_STOPWORDS,
|
||||
query: BRAND_STOPWORDS[query] ?? query,
|
||||
};
|
||||
|
||||
Vendored
+3
-3
@@ -6,13 +6,13 @@ declare module '*.vue' {
|
||||
}
|
||||
|
||||
declare module '*.data.ts' {
|
||||
const data: any;
|
||||
const data: unknown;
|
||||
|
||||
export { data };
|
||||
}
|
||||
|
||||
declare module '*.data' {
|
||||
const data: any;
|
||||
const data: unknown;
|
||||
|
||||
export { data };
|
||||
}
|
||||
@@ -75,7 +75,7 @@ declare global {
|
||||
doNotShowAfterSubmit?: boolean;
|
||||
customFormUrl?: string; // when you want to load the form via it's custom domain URL
|
||||
hiddenFields?: {
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ Contribution in form of code or new icons is always welcome, but you can also he
|
||||
|
||||
### 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.
|
||||
If you have design skills, consider contributing new icons to the Lucide library. Check out our [icon design guide](/contribute/icons/design-principles.md) for tips and guidelines on creating icons that fit the Lucide style.
|
||||
|
||||
### Contribute Code
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: Affinity Designer Template Guide
|
||||
---
|
||||
|
||||
# Affinity Designer Template Guide
|
||||
|
||||
This guide describes how to use the Affinity Designer template for Lucide.
|
||||
|
||||
## General Workflow
|
||||
|
||||
>Attention: By default, Affinity Designer sets the unit for stroke to points. Make sure that it is set to pixel. To do this, open `Preferences > User Interface`. Under `Decimal Places for Unit Types`, uncheck `Show Lines in points`.
|
||||
|
||||
1. Download and open the [Affinity Designer template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/affinity_designer.aftemplate).
|
||||
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.
|
||||
|
||||

|
||||
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`).
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
title: Figma Template Guide
|
||||
---
|
||||
|
||||
# Figma Template Guide
|
||||
|
||||
This guide shows the steps to setup Figma for creating icons that conform to the Featherity design guidelines.
|
||||
|
||||
## Setting Up The Frame
|
||||
When you create a new document in Figma, the document. Each individual icon you want to create, has to be created in a separate frame.
|
||||
|
||||
To do this, create a frame of 24x24 pixels.
|
||||
|
||||
1. Click the frame button (or press `F`)
|
||||
2. Draw a 24x24 frame (or edit it afterwards from the design window)
|
||||
|
||||
In this newly created frame, you will create your icon. If you want, you can change the name of your frame to the name of the icon you are going to create. Then it will be exported as `FRAME-NAME.svg`.
|
||||
|
||||
## Create Your icon
|
||||
To design your icon in the style of Feather Icons, you need to adjust a few settings in Figma.
|
||||
|
||||
Draw in your new frame with the pen tool. You can open it with the window at the top, or with the shortcut `P`. Once you click in your frame, you can adjust the settings for the pen tool in the design-window on the right.
|
||||
|
||||
Set the following:
|
||||
1. Vector
|
||||
1. Corner radius: 2px
|
||||
2. Stroke
|
||||
1. Stroke width: 2px
|
||||
2. Stroke alignment: center
|
||||
|
||||

|
||||
|
||||
## Export Or Copy Your Icon
|
||||
Once you have completed your icon, you can export it.
|
||||
|
||||
1. Select the frame
|
||||
2. Open the *Export* tab on the right
|
||||
3. Set the file type as SVG
|
||||
4. Press export
|
||||
|
||||
Or you can also copy its source as SVG.
|
||||
|
||||
1. Select the frame
|
||||
2. Right click it
|
||||
3. Click on *Copy/Paste as*
|
||||
4. Click on *Copy as SVG*
|
||||
|
||||
That's it. You just made your first icon. Congratulations!
|
||||
|
||||
## Figma Tips
|
||||
1. The [Icon Design Guidelines](icon-design-guide.md) dictate that you keep 2px spacing between detached elements. In Figma, you can easily check this with: `⌥` Option (MacOS) or `Alt` (Windows).
|
||||
@@ -1,199 +0,0 @@
|
||||
---
|
||||
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
|
||||
|
||||
Here are rules that should be followed to keep quality and consistency when making icons for Lucide.
|
||||
|
||||
### 1. Icons must be designed on a **24 by 24 pixels** canvas.
|
||||
|
||||

|
||||
|
||||
### 2. Icons must have at least **1 pixel padding** within the canvas.
|
||||
|
||||

|
||||
|
||||
### 3. Icons must have a **stroke width of 2 pixels**.
|
||||
|
||||

|
||||
|
||||
### 4. Icons must use **round joins**.
|
||||
|
||||

|
||||
|
||||
### 5. Icons must use **round caps**.
|
||||
|
||||

|
||||
|
||||
### 6. Icons must use **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
|
||||
|
||||

|
||||
|
||||
#### B. **1 pixel** if they are smaller than 8 pixels in size
|
||||
|
||||

|
||||
|
||||
### 8. Distinct elements must have **2 pixels of spacing between each other**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 9. Icons should have a similar optical volume to `circle` and `square`.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
**Tip:** whenever possible align elements and arc centers to the grid.
|
||||
|
||||
### 14. Icons should share common shapes
|
||||
|
||||
You should try to create consistent groups and variants, reuse and try to create uniformity.
|
||||
Consistency inside groups and variants has a lower priority than the rules above.
|
||||
|
||||
**Example:** All `-off` icons should look the same unless it for example violates the optical volume rule.
|
||||
|
||||
**Tip:** Try to not move the base shape to enable better use in a toggle context.
|
||||
|
||||
## Naming conventions
|
||||
|
||||
1. Icon names use lower kebab case.\
|
||||
For example: `arrow-up` instead of `Arrow Up`.
|
||||
|
||||
2. Icon names use International English names, as opposed to local variants.\
|
||||
For example: `color` instead of `colour`.
|
||||
|
||||
3. Icons should be named for what they depict rather than their use case or what they represent.\
|
||||
For example: `floppy-disk` instead of `save` and `circle-slash` rather than `ban`.
|
||||
|
||||
4. Icons that are part of a group are named `<group>-<variant>`.\
|
||||
For example: `badge-plus` is based on `badge`.
|
||||
|
||||
5. Icon names for alternate icons should represent what makes the alternate unique instead of being numbered.\
|
||||
For example: `send-horizontal` instead of `send-2`.
|
||||
|
||||
6. Names containing numerals are not allowed, unless the number itself is represented in the icon.\
|
||||
For example: `arrow-down-0-to-1` contains both numerals.
|
||||
|
||||
7. Icons depicting multiple elements (e.g. a person and a circle) of different sizes must list these elements in decreasing order of size.\
|
||||
For example: if the circle is bigger, it should be `circle-person`, if the person is bigger, it should be `person-circle`.
|
||||
|
||||
8. Icons depicting multiple elements of roughly equal sizes (e.g. a `ruler` and a `pencil`) must list these elements front to back in case one element is in front of the other, otherwise in English reading order (top to bottom, left to right).\
|
||||
For example: if the `pencil` is either in front of, above or left of `ruler`, it should be `pencil-ruler`, otherwise, it should be `ruler-pencil`.
|
||||
|
||||
9. Icons depicting some sort of variation of an element must use the `[element]-[modifier]` naming scheme, with modifiers being applied to each element respectively.\
|
||||
For example: a dashed circle must be named `circle-dashed`, not `dashed-circle`, and in coordination with the previous guidelines, a dashed circle containing a broken heart would be named `circle-dashed-heart-broken`, due to the heart being smaller than the circle.
|
||||
|
||||
## Code Conventions
|
||||
|
||||
Before an icon is added to the library, we like to have readable and optimized SVG code.
|
||||
|
||||
### Global Attributes
|
||||
|
||||
For each icon these attributes are applied, corresponding to the above rules.
|
||||
|
||||
```xml
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<!-- SVGElements -->
|
||||
</svg>
|
||||
```
|
||||
|
||||
### Minify paths
|
||||
|
||||
The code of paths can sometimes get quite large. To reduce file size we like to minify the code.
|
||||
We recommend to use [Lucide Studio](https://studio.lucide.dev/) to tidy paths to 3 points of precision.
|
||||
|
||||
### Allowed elements
|
||||
|
||||
SVG files may only contain simple path and shape elements, which may not have any attributes other than sizing and spacing.\
|
||||
In practice only the following elements and attributes are allowed:
|
||||
* `<path d>`
|
||||
* `<line x1 x2>`
|
||||
* `<polygon points>`
|
||||
* `<polyline points>`
|
||||
* `<circle cx cy r>`
|
||||
* `<ellipse cx cy rx ry>`
|
||||
* `<rect x y width height rx>`
|
||||
|
||||
This also means that no transforms, filters, fills or explicit strokes are allowed.
|
||||
|
||||
Never use [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use). While it may sometimes seem like a good way to optimize file size, there's no way to ensure that the referenced element IDs will be unique once the SVGs are embedded in HTML documents.
|
||||
|
||||
## JSON metadata descriptor
|
||||
|
||||
Each icon added must also come with a matching JSON file listing tags and categories for the icon.
|
||||
Please use the following template:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"github-username",
|
||||
"another-github-username"
|
||||
],
|
||||
"tags": [
|
||||
"foo",
|
||||
"bar"
|
||||
],
|
||||
"categories": [
|
||||
"devices"
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
description: Learn how to use the Affinity Designer template for Lucide.
|
||||
---
|
||||
|
||||
# Affinity Designer template guide
|
||||
|
||||
This guide explains how to use the Affinity Designer template for Lucide.
|
||||
|
||||
## Workflow
|
||||
|
||||
> Affinity Designer uses points for strokes by default. Change strokes to pixels before drawing. Open `Preferences > User Interface`, then under `Decimal Places for Unit Types`, uncheck `Show Lines in points`.
|
||||
|
||||
1. Download and open the [Affinity Designer template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/affinity_designer.aftemplate).
|
||||
2. Follow the [design language](design-principles.md) while designing your icon.
|
||||
3. Export the file as SVG with `File > Export`. Set rasterization to _Nothing_, check _Export text as curves_, check _Use hex colors_, and check _Flatten transforms_.
|
||||
|
||||

|
||||
|
||||
4. Optimize the exported SVG with [Lucide Studio](https://studio.lucide.dev/).
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
description: Learn about allowed SVG elements and attributes, optimization, and other code conventions.
|
||||
---
|
||||
|
||||
# SVG conventions
|
||||
|
||||
Before you add an icon to Lucide, make sure its SVG is readable and optimized.
|
||||
|
||||
## Global attributes
|
||||
|
||||
Each icon uses these root SVG attributes:
|
||||
|
||||
```xml
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<!-- SVGElements -->
|
||||
</svg>
|
||||
```
|
||||
|
||||
## Minify paths
|
||||
|
||||
Path data can get large. Minify it to keep SVG files small.
|
||||
|
||||
Use [Lucide Studio](https://studio.lucide.dev/?utm_source=lucide.dev&utm_medium=design-guide) to tidy paths to 3 decimal places.
|
||||
|
||||
## Allowed elements
|
||||
|
||||
SVG files may only contain simple path and shape elements. These elements may only use sizing and position attributes.
|
||||
|
||||
Only these elements and attributes are allowed:
|
||||
|
||||
- `<path d>`
|
||||
- `<line x1 y1 x2 y2>`
|
||||
- `<polygon points>`
|
||||
- `<polyline points>`
|
||||
- `<circle cx cy r>`
|
||||
- `<ellipse cx cy rx ry>`
|
||||
- `<rect x y width height rx>`
|
||||
|
||||
Do not use transforms, filters, fills, or explicit stroke colors.
|
||||
|
||||
Never use [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use). It can reduce file size, but referenced IDs may conflict when SVGs are embedded in HTML.
|
||||
|
||||
## Metadata file
|
||||
|
||||
Each icon must also have a matching JSON file with contributors, use cases, tags, and categories.
|
||||
|
||||
See [metadata conventions](./metadata-conventions/) for details.
|
||||
|
||||
## Validation
|
||||
|
||||
Before submitting SVG changes, format and validate the icon files:
|
||||
|
||||
```sh
|
||||
pnpm run lint:icons
|
||||
```
|
||||
|
||||
You can automatically format SVG files with Prettier:
|
||||
|
||||
```sh
|
||||
pnpm run lint:icons-fix
|
||||
```
|
||||
|
||||
To check icon names, categories, metadata, and SVG structure together, run:
|
||||
|
||||
```sh
|
||||
pnpm run lint:icons:all
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
Before opening a pull request, confirm that:
|
||||
|
||||
- The SVG uses the standard `24 × 24` viewBox.
|
||||
- The SVG uses `fill="none"` and `stroke="currentColor"`.
|
||||
- Stroke width, line caps, and line joins match the Lucide defaults.
|
||||
- Paths are tidy and use appropriate numeric precision.
|
||||
- The SVG uses only allowed elements and attributes.
|
||||
- The SVG does not use transforms, filters, fills, explicit strokes, or `<use>`.
|
||||
- The icon has a matching JSON metadata file.
|
||||
- `pnpm run lint:icons` passes.
|
||||
- `pnpm run lint:json:icons` passes.
|
||||
@@ -0,0 +1,403 @@
|
||||
---
|
||||
description: Learn the visual principles that make Lucide icons consistent and recognizable.
|
||||
---
|
||||
|
||||
# Lucide design language
|
||||
|
||||
Lucide is a consistent icon set, not a collection of unrelated icons. Every icon should feel like it belongs with the rest of the library.
|
||||
|
||||
This guide explains the visual rules behind Lucide icons and how to use them when designing new icons.
|
||||
|
||||
::: info Requirement levels
|
||||
This guide uses normative terms such as **must**, **should**, and **may** as defined in [RFC 2119](https://www.rfc-editor.org/info/rfc2119/).
|
||||
:::
|
||||
|
||||
## 1. Use a 24 × 24-pixel canvas
|
||||
|
||||
Icons **must** be designed on a 24 × 24-pixel canvas.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/24px-24px.svg?raw=true
|
||||
Use a **24 × 24 pixel** canvas.
|
||||
:::
|
||||
::: dont ../../images/24px-24px-violation.svg?raw=true
|
||||
Don't use a canvas that is larger, smaller, or not square.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 2. Keep a 1-pixel safe zone
|
||||
|
||||
Icons **must** have at least 1 pixel of padding between their strokes and the edge of the canvas.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/1px-padding.svg?raw=true
|
||||
Keep at least **1 pixel of padding** around the icon.
|
||||
:::
|
||||
::: dont ../../images/1px-padding-violation.svg?raw=true
|
||||
Don't place strokes at the edge of the canvas. They may get clipped.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 3. Use 2-pixel strokes
|
||||
|
||||
Strokes **must** be 2 pixels wide.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/2px-stroke.svg?raw=true
|
||||
Use a **2-pixel stroke width**.
|
||||
:::
|
||||
::: dont ../../images/2px-stroke-violation.svg?raw=true
|
||||
Don't use **thicker or thinner** strokes, or **mix stroke widths**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 4. Use round line joins
|
||||
|
||||
Strokes **must** use round line joins.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/round-joints.svg?raw=true
|
||||
Use **round line joins**.
|
||||
:::
|
||||
::: dont ../../images/round-joints-violation.svg?raw=true
|
||||
Don't use **miter or bevel** line joins.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
Round joins do not replace corner rounding. Most sharp corners still need a radius.
|
||||
:::
|
||||
|
||||
## 5. Use round line caps
|
||||
|
||||
Open paths **must** use round line caps.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/round-caps.svg?raw=true
|
||||
Use **round line caps** on open paths.
|
||||
:::
|
||||
::: dont ../../images/round-caps-violation.svg?raw=true
|
||||
Don't use **butt or square** line caps.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 6. Use centered strokes
|
||||
|
||||
Strokes **must** be centered on their paths.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/centered-strokes.svg?raw=true
|
||||
Keep strokes **centered** on their paths.
|
||||
:::
|
||||
::: dont ../../images/centered-strokes-violation.svg?raw=true
|
||||
Don't use **inside or outside** strokes. They change how line joins appear, creating sharp inner corners.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 7. Use consistent corner radii
|
||||
|
||||
Corner radius depends on the size and angle of the element.
|
||||
|
||||
### 7.1. Sharp corners **should** be rounded unless the geometry of the icon requires otherwise
|
||||
|
||||
:::: example
|
||||
::: do ../../images/border-radius-correct.svg?raw=true
|
||||
Use **rounded corners**.
|
||||
:::
|
||||
::: dont ../../images/border-radius-violation.svg?raw=true
|
||||
Don't use **sharp corners**.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.2. Elements at least 8 pixels wide or tall **should** use a 2-pixel corner radius for 90° corners
|
||||
|
||||
:::: example
|
||||
::: do ../../images/2px-border-radius.svg?raw=true
|
||||
Use a **2-pixel corner radius** for shapes that are at least 8 pixels wide or tall.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.3. Elements smaller than 8 pixels **should** use a 1-pixel corner radius for 90° corners
|
||||
|
||||
Use a **1-pixel corner radius** for shapes smaller than 8 pixels.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/1px-border-radius.svg?raw=true
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.4. Diagonal lines meeting at a 90° angle **should** use a radius that preserves pixel-grid alignment
|
||||
|
||||
Diagonal lines that meet at a right angle usually need a **2.41 pixel** radius (1+√2) to stay aligned to the grid.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/border-radius-90deg.svg?raw=true
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.5. Other acute corners **should** use an appropriate amount of rounding based on their geometry
|
||||
|
||||
Even **very sharp corners** usually need a small amount of rounding. The exact amount depends on the shape.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/border-radius-arbitrary.svg?raw=true
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.6. Corners where lines cross **must not** be rounded
|
||||
|
||||
Corners where multiple lines meet **must not** be rounded. Keep corners sharp where multiple lines meet so the icon scales well.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/border-radius-line-join.svg?raw=true
|
||||
Keep corners sharp where multiple lines meet so the icon scales well.
|
||||
:::
|
||||
::: dont ../../images/border-radius-line-join-violation.svg?raw=true
|
||||
Don't round corners where more than two lines meet. This can make the icon scale poorly.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 7.7. Non-outlined arrows **must not** be rounded
|
||||
|
||||
Non-outlined arrows **must not** be rounded.
|
||||
|
||||
If the arrow is an outline shape, it **should** be rounded.
|
||||
|
||||
## 8. Keep at least 2 pixels between elements
|
||||
|
||||
### 8.1. Distinct elements **must** have at least 2 pixels of visual spacing between them
|
||||
|
||||
:::: example
|
||||
::: do ../../images/2px-element-spacing.svg?raw=true
|
||||
Keep **2 pixels of spacing** between distinct elements.
|
||||
:::
|
||||
::: dont ../../images/1px-element-spacing.svg?raw=true
|
||||
Don't use gaps smaller than **2 pixels** between distinct elements.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 8.2. Inner gaps **should** be at least 2 pixels wide
|
||||
|
||||
The 2-pixel gap rule also applies inside shapes. Most inner gaps **should** be at least 2 pixels wide.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/2px-inner-spacing.svg?raw=true
|
||||
Keep at least **2 pixels of spacing** inside shapes.
|
||||
:::
|
||||
::: dont ../../images/1px-inner-spacing.svg?raw=true
|
||||
Don't use inner gaps smaller than **2 pixels**.
|
||||
:::
|
||||
::::
|
||||
|
||||
You can test this by placing a **2-pixel-wide circle** inside your shape.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/2px-inner-gap.svg?raw=true
|
||||
If a 2-pixel-wide circle fits **without overlapping**, the gap is **wide enough**.
|
||||
:::
|
||||
::: dont ../../images/1px-inner-gap.svg?raw=true
|
||||
If the circle **overlaps** the shape, the gap should be **wider**.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 8.3. Visual spacing **should** remain consistent when elements connect or intersect
|
||||
|
||||
Spacing **should** remain visually consistent where elements connect or intersect. Elements **must not** connect without keeping at least 2 pixels of spacing, and **should not** terminate with an abrupt cut.
|
||||
|
||||
:::: example
|
||||
::: dont ../../images/2px-element-spacing-connected.svg?raw=true
|
||||
Don't connect elements without keeping **2 pixels of spacing**.
|
||||
:::
|
||||
::: dont ../../images/2px-element-spacing-abrupt-cut.svg?raw=true
|
||||
Don't use **abrupt cuts** where one element meets another.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 8.4. Off-variants **may** omit the 2-pixel gap rule
|
||||
|
||||
The `-off` variants have their own design language and **may** omit the 2-pixel gap rule.
|
||||
|
||||
## 9. Aim for consistent visual weight
|
||||
|
||||
Icons **should** have a similar visual weight when viewed alongside each other.
|
||||
|
||||
:::: example
|
||||
|
||||
::: do ../../images/visual-weight-consistent.svg?raw=true
|
||||
Match the visual weight of `circle` and `square`.
|
||||
:::
|
||||
|
||||
::: dont ../../images/visual-weight-uneven.svg?raw=true
|
||||
The first icon is **too large**. The last one is **too small**.
|
||||
:::
|
||||
|
||||
::::
|
||||
|
||||
::: tip
|
||||
The amount and placement of strokes affect how heavy an icon looks. An icon with many close strokes can look heavier than a simpler icon, even with the same stroke width.
|
||||
|
||||
Place your icon next to `circle` or `square`, then blur both icons. Your icon should not look much lighter or darker than the reference icon.
|
||||
:::
|
||||
|
||||
## 10. Balance icons visually
|
||||
|
||||
Icons **should** be visually centered within the canvas.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/visually-centered-do.svg?raw=true
|
||||
Keep your icons visually centered.
|
||||
:::
|
||||
::: dont ../../images/visually-centered-dont.svg?raw=true
|
||||
This icon feels shifted toward the top left.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do ../../images/visually-centered-symmetrical-do.svg?raw=true
|
||||
Symmetrical icons **should** remain geometrically centered.
|
||||
:::
|
||||
::: dont ../../images/visually-centered-symmetrical-dont.svg?raw=true
|
||||
Don't place symmetrical icons off-center for no good reason.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
Geometric centering does not always look centered. Asymmetrical icons **may** be moved slightly off center so they look visually centered.
|
||||
|
||||
Compare your icon with `circle` or `square` side by side and stacked vertically. If it looks shifted, adjust it.
|
||||
:::
|
||||
|
||||
## 11. Keep density low
|
||||
|
||||
Icons **should** have a level of detail similar to other Lucide icons.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/density-optimal.svg?raw=true
|
||||
Simplify complex subjects, but keep the features that make them recognizable.
|
||||
:::
|
||||
::: dont ../../images/density-too-dense.svg?raw=true
|
||||
Don't add extra details that make the design feel crowded.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
|
||||
Dense areas and close strokes can make an icon look heavier than nearby icons.
|
||||
|
||||
View the icon at its intended size and blur it. Areas that turn dark may have too much detail.
|
||||
|
||||
:::
|
||||
|
||||
## 12. Use smooth, simple curves
|
||||
|
||||
Curves **should** be smooth and as simple as the shape allows. Arcs and quadratic Bézier curves **should** be preferred where possible. Cubic Bézier curves **may** be used where necessary.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/curvature-smooth.svg?raw=true
|
||||
Prefer **simple arcs** and **quadratic curves** where possible.
|
||||
:::
|
||||
::: dont ../../images/curvature-uneven.svg?raw=true
|
||||
Don't use **uneven or unnecessarily complex** curves.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
When you need cubic Bézier curves, align their control points to avoid visible bumps.
|
||||
|
||||
Avoid extra control points. They make shapes harder to edit and can create small flaws.
|
||||
:::
|
||||
|
||||
## 13. Design for the pixel grid
|
||||
|
||||
Lucide icons **should** look sharp at small sizes, including on low-density displays. Coordinates and centers of arcs and other geometric elements **should** align to the pixel grid where possible.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/pixel-perfection-ideal.svg?raw=true
|
||||
Align coordinates, arc centers, and other geometric elements to the pixel grid when possible.
|
||||
:::
|
||||
::: dont ../../images/pixel-perfection-bad.svg?raw=true
|
||||
Don't place geometry off the pixel grid without a visual reason.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do ../../images/pixel-perfection-subelement-do.svg?raw=true
|
||||
Also **align subelements** to the pixel grid.
|
||||
:::
|
||||
::: dont ../../images/pixel-perfection-subelement-dont.svg?raw=true
|
||||
Don't place subelements off-grid. They may become blurry on low-density displays.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do ../../images/pixel-perfection-diagonals-do.svg?raw=true
|
||||
In general try to **align every diagonal line** to the pixel grid.
|
||||
:::
|
||||
::: dont ../../images/pixel-perfection-diagonals-dont.svg?raw=true
|
||||
Avoid placing diagonal lines off-grid. They will appear blurry and optically wider on low-density displays.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
Pixel alignment **should** support the icon. Off-grid geometry **may** be used when required for visual balance, recognizable geometry, or smooth curvature. Pixel alignment **should not** take precedence over the visual quality of the icon.
|
||||
:::
|
||||
|
||||
## 14. Reuse established shapes
|
||||
|
||||
### 14.1. Variants **should** preserve base icon geometry
|
||||
|
||||
When creating a variant, the base icon geometry, placement, and orientation **should** be preserved unless the new icon requires a change.
|
||||
|
||||
:::: example
|
||||
::: do ../../images/established-shapes-base-do.svg?raw=true
|
||||
Reuse existing base icons **exactly as they appear**.
|
||||
:::
|
||||
::: dont ../../images/established-shapes-base-dont.svg?raw=true
|
||||
Don't change the **geometry, placement, or orientation** of base shapes.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 14.2. Related icons **should** share shapes and visual patterns when possible
|
||||
|
||||
:::: example
|
||||
::: do ../../images/established-shapes-sub-do.svg?raw=true
|
||||
Reuse elements from existing icons **without change**.
|
||||
:::
|
||||
::: dont ../../images/established-shapes-sub-dont.svg?raw=true
|
||||
Don't redesign **existing elements**.
|
||||
:::
|
||||
::::
|
||||
|
||||
### 14.3. Common modifiers and elements **should** look consistent across the library
|
||||
|
||||
:::: example
|
||||
::: do ../../images/established-shapes-consistency-do.svg?raw=true
|
||||
Keep add-ons consistent in **placement and size**.
|
||||
:::
|
||||
::: dont ../../images/established-shapes-consistency-dont.svg?raw=true
|
||||
Don't use **irregular placement or size** compared to existing icons.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
Preserving the base shape makes related icons work better when users switch between them.
|
||||
|
||||
Existing geometry **may** be modified when necessary to satisfy other Lucide design language rules. Consistency with an existing icon **should not** override clarity, balance, or readability.
|
||||
:::
|
||||
|
||||
## Summary
|
||||
|
||||
When designing a Lucide icon:
|
||||
|
||||
1. **Use a 24 × 24 pixel canvas** with at least **1 pixel of safe zone**.
|
||||
2. **Use 2 pixel centered strokes** with **round caps and joins**.
|
||||
3. **Round sharp corners** using consistent radii appropriate for their size and geometry.
|
||||
4. **Keep at least 2 pixels of spacing** between distinct elements and, where possible, inside shapes.
|
||||
5. **Match the visual weight** of `circle` and `square`.
|
||||
6. **Center icons visually**, accounting for visual weight.
|
||||
7. **Keep visual density low** and remove unnecessary detail.
|
||||
8. **Use smooth, simple curves** without unnecessary control points.
|
||||
9. **Align geometry to the pixel grid** where possible.
|
||||
10. **Reuse established shapes** from existing Lucide icons.
|
||||
|
||||
When these guidelines conflict, prioritize **clarity, visual balance, and consistency** with the rest of Lucide.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
description: Learn how to create Lucide icons in Figma.
|
||||
---
|
||||
|
||||
# Figma template guide
|
||||
|
||||
This guide shows how to set up Figma for Lucide icons.
|
||||
|
||||
## Set up the frame
|
||||
|
||||
Create each icon in its own frame.
|
||||
|
||||
Use a `24 × 24` pixel frame.
|
||||
|
||||
1. Click the frame button, or press `F`.
|
||||
2. Draw a `24 × 24` frame, or set the size in the right sidebar.
|
||||
|
||||
Design the icon inside this frame. Name the frame after the icon so Figma exports it as `frame-name.svg`.
|
||||
|
||||
## Create your icon
|
||||
|
||||
Set up the stroke before drawing.
|
||||
|
||||
Draw in the frame with the pen tool. Open it from the toolbar or press `P`. After you start drawing, adjust the vector settings in the right sidebar.
|
||||
|
||||
Set the following:
|
||||
|
||||
1. Vector
|
||||
1. Corner radius: 2px
|
||||
2. Stroke
|
||||
1. Stroke width: 2px
|
||||
2. Stroke alignment: center
|
||||
|
||||

|
||||
|
||||
## Export or copy your icon
|
||||
|
||||
When your icon is ready, export it.
|
||||
|
||||
1. Select the frame.
|
||||
2. Open the _Export_ section in the right sidebar.
|
||||
3. Set the file type to SVG.
|
||||
4. Click _Export_.
|
||||
|
||||
You can also copy the SVG source.
|
||||
|
||||
1. Select the frame.
|
||||
2. Right-click it.
|
||||
3. Choose _Copy/Paste as_.
|
||||
4. Choose _Copy as SVG_.
|
||||
|
||||
You now have an SVG to prepare for contribution.
|
||||
|
||||
## Optimize your icon
|
||||
|
||||
You can optimize the exported SVG with [Lucide Studio](https://studio.lucide.dev/).
|
||||
|
||||
## Check spacing in Figma
|
||||
|
||||
The [design language](design-principles.md) requires at least 2 pixels of spacing between separate elements. In Figma, hold `Option` on macOS or `Alt` on Windows to check spacing.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
description: Learn how to use the Adobe Illustrator template for Lucide.
|
||||
---
|
||||
|
||||
# Adobe Illustrator template guide
|
||||
|
||||
This guide explains how to use the Adobe Illustrator template for Lucide.
|
||||
|
||||
> Templates can fall out of date. Check the [design language](design-principles.md) before you start.
|
||||
|
||||
## Workflow
|
||||
|
||||
The Illustrator template follows the [design language](design-principles.md).
|
||||
|
||||
1. Download and open the [Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
|
||||
|
||||
2. Remove the content from the example logo layer (`Draw`) and start drawing.
|
||||
|
||||
3. Check that your icon follows the [design language](design-principles.md).
|
||||
|
||||
4. Before export, remove unnecessary layers, especially `Padding` and `Grid`.
|
||||
|
||||
5. Export the file with `Export > Export As...`, then save it as SVG. Use these SVG options:
|
||||
|
||||

|
||||
|
||||
6. Optimize your SVG with [Lucide Studio](https://studio.lucide.dev/)
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
description: Guidelines and instructions for contributing icons to Lucide
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import OverviewLink from '../../.vitepress/theme/components/base/OverviewLink.vue';
|
||||
import OverviewLinkGrid from '../../.vitepress/theme/components/base/OverviewLinkGrid.vue';
|
||||
import { resourcesSidebar } from '../../.vitepress/sidebar/resources.ts';
|
||||
</script>
|
||||
|
||||
# Designing icons for Lucide
|
||||
|
||||
Every icon should feel like it belongs with the rest of Lucide.
|
||||
|
||||
This guide explains how to design and prepare icons for Lucide.
|
||||
|
||||
## What this guide covers
|
||||
|
||||
<OverviewLinkGrid>
|
||||
<OverviewLink v-for="item in resourcesSidebar[3].items.slice(1, -1)" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
|
||||
</OverviewLinkGrid>
|
||||
|
||||
## Use your preferred design tool
|
||||
|
||||
You can design Lucide icons with any vector editor that can export SVG.
|
||||
|
||||
Use these step-by-step guides for common tools:
|
||||
|
||||
<OverviewLinkGrid>
|
||||
<OverviewLink v-for="item in resourcesSidebar[2].items.at(-1).items" :key="item.link" :href="item.link" :title="item.text" :desc="item.desc"/>
|
||||
</OverviewLinkGrid>
|
||||
|
||||
These guides explain how to set up your document, follow the Lucide rules, and export a usable SVG.
|
||||
|
||||
Regardless of which tool you use, the same design and SVG requirements apply.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
description: Learn how to create Lucide icons in Inkscape.
|
||||
---
|
||||
|
||||
# Inkscape setup guide
|
||||
|
||||
This guide shows how to set up Inkscape for Lucide icons.
|
||||
|
||||
## Set up the canvas
|
||||
|
||||
Inkscape creates new documents with a default canvas size. Change it to `24 × 24` pixels:
|
||||
|
||||
1. Open the Document Properties dialog with `File > Document Properties`.
|
||||
2. On the _Page Size_ tab, under _Custom Size_, set units to `px` and set width and height to `24`.
|
||||

|
||||
3. On the _Grid_ tab, select `Rectangular Grid` and click _New Grid_.
|
||||

|
||||
4. Set grid units to `px`, then set `Spacing X` and `Spacing Y` to `1`.
|
||||

|
||||
5. Close the Document Properties dialog.
|
||||
6. To center the canvas in the viewport, select `View > Zoom > Drawing`.
|
||||
|
||||
## Set up paths
|
||||
|
||||
1. Create a path or shape.
|
||||
2. With the path selected, press `Ctrl+Shift+F` to open the Stroke and Fill panel.
|
||||

|
||||
3. On the _Stroke Style_ tab:
|
||||
- Set stroke width to `2px`.
|
||||
- Select the rounded join type.
|
||||
- Select the rounded cap type.
|
||||
4. For rectangles, select the rectangle and set `Rx` and `Ry` to `2px` in the toolbar.
|
||||

|
||||
|
||||
## Save a file
|
||||
|
||||
1. When you are ready, click _Save As_ and choose _Optimized SVG_ as the file type.
|
||||

|
||||
|
||||
## Optimize your icon
|
||||
|
||||
As a final step, optimize your SVG with [Lucide Studio](https://studio.lucide.dev/).
|
||||
@@ -0,0 +1,169 @@
|
||||
---
|
||||
description: Learn how to write clear and consistent metadata for Lucide icons.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import OverviewLink from '../../../.vitepress/theme/components/base/OverviewLink.vue';
|
||||
import OverviewLinkGrid from '../../../.vitepress/theme/components/base/OverviewLinkGrid.vue';
|
||||
</script>
|
||||
|
||||
# Metadata conventions
|
||||
|
||||
Every Lucide icon has a matching JSON metadata file. Metadata credits contributors, groups icons, improves search, and explains where an icon is useful.
|
||||
|
||||
Use this guide when you add a new icon or review existing metadata.
|
||||
|
||||
## Metadata file location
|
||||
|
||||
Each icon metadata file must live next to its SVG file in the `icons` directory.
|
||||
|
||||
The JSON file must use the same base name as the SVG file:
|
||||
|
||||
- `home.svg` -> `home.json`
|
||||
- `arrow-up.svg` -> `arrow-up.json`
|
||||
- `square-parking.svg` -> `square-parking.json`
|
||||
|
||||
All icon metadata files must pass `icon.schema.json`.
|
||||
|
||||
## Required fields
|
||||
|
||||
Every icon metadata file must include:
|
||||
|
||||
- `$schema`
|
||||
- `contributors`
|
||||
- `use-cases`
|
||||
- `tags`
|
||||
- `categories`
|
||||
|
||||
A minimal metadata file looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": ["github-username", "another-github-username"],
|
||||
"use-cases": ["Representing example items in an interface"],
|
||||
"tags": ["foo", "bar"],
|
||||
"categories": ["devices"]
|
||||
}
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
The `contributors` field lists GitHub usernames for people who created or meaningfully changed the icon.
|
||||
|
||||
### Contributor rules
|
||||
|
||||
1. If you create a new icon based on existing ones, add all contributors from all base icons.
|
||||
2. Add a username when someone made a significant change to the icon design.
|
||||
3. Do not add people for minor review comments, metadata-only edits, or unrelated discussion.
|
||||
|
||||
## Use cases
|
||||
|
||||
The `use-cases` field explains where and why someone would use the icon in an interface.
|
||||
|
||||
Use cases should be short phrases, not full sentences. Start with an `-ing` verb, such as:
|
||||
|
||||
- Representing [concept]
|
||||
- Indicating [state]
|
||||
- Marking [item]
|
||||
- Showing [information]
|
||||
- Toggling [setting]
|
||||
|
||||
### Use case rules
|
||||
|
||||
- Keep each use case focused on one idea
|
||||
- Keep entries concise and specific
|
||||
- Write from the interface's perspective
|
||||
- Describe real usage, not the icon name or appearance
|
||||
- Add context when it clarifies meaning
|
||||
- Do not end with punctuation
|
||||
- Do not add Markdown or emoji
|
||||
- Do not add implementation details or pull request references
|
||||
|
||||
<OverviewLinkGrid>
|
||||
<OverviewLink href="./use-case-guide.md" title="Use case guide" desc="Learn the best practices for writing use cases"/>
|
||||
</OverviewLinkGrid>
|
||||
|
||||
## Tags
|
||||
|
||||
The `tags` field is a list of search terms for the icon.
|
||||
|
||||
Tags should describe concepts, synonyms, related terms, and common contexts.
|
||||
|
||||
Check existing tags before adding new ones. This keeps search consistent and avoids duplicates.
|
||||
|
||||
### Tag rules
|
||||
|
||||
- Use lowercase tags.
|
||||
- Prefer single words where possible.
|
||||
- Multi-word tags are allowed when they are clearer, such as `magnifying glass`.
|
||||
- Do not include the icon name as a tag.
|
||||
- Do not add generic tags like `icon`.
|
||||
- Do not add unrelated concepts just to increase search coverage.
|
||||
|
||||
For example, `mail-search` can use tags like `email`, `message`, `letter`, `find`, and `filter`, but not `mail`, `search` or `mail search`.
|
||||
|
||||
<OverviewLinkGrid>
|
||||
<OverviewLink href="./tag-guide.md" title="Tag guide" desc="Learn the best practices for writing tags"/>
|
||||
</OverviewLinkGrid>
|
||||
|
||||
## Categories
|
||||
|
||||
The `categories` field groups icons by broad use.
|
||||
|
||||
Categories must come from the allowed category list in `icon.schema.json`.
|
||||
|
||||
Choose categories based on what the icon represents and where people use it. Check similar icons first.
|
||||
|
||||
### Category rules
|
||||
|
||||
- Use only categories allowed by `icon.schema.json`.
|
||||
- Use lowercase category names.
|
||||
- Do not invent new categories in icon metadata.
|
||||
- Choose relevant categories only.
|
||||
|
||||
## Variants and related icons
|
||||
|
||||
Related icons should have related metadata, but do not copy use cases blindly.
|
||||
|
||||
Write metadata for the specific variant:
|
||||
|
||||
- `battery-low` -> `Indicating a low battery charge level`
|
||||
- `battery-full` -> `Indicating a full battery charge level`
|
||||
- `square-arrow-right-enter` -> sign in, enter, or join contexts
|
||||
- `square-arrow-right-exit` -> sign out, exit, or export contexts
|
||||
|
||||
When a request covers several icons, assign each use case only to the icon it describes. Do not copy metadata to icons it does not describe.
|
||||
|
||||
## Aliases
|
||||
|
||||
Some icons can include an optional `aliases` field for alternate names.
|
||||
|
||||
Only add aliases when an icon has been renamed from a non-compliant name.
|
||||
|
||||
## Validation
|
||||
|
||||
Before submitting metadata changes, validate the icon JSON files:
|
||||
|
||||
```sh
|
||||
pnpm run lint:json:icons
|
||||
```
|
||||
|
||||
You can also format changed files with Prettier:
|
||||
|
||||
```sh
|
||||
pnpm exec prettier "icons/*.json" --check
|
||||
```
|
||||
|
||||
## Checklist
|
||||
|
||||
Before opening a pull request, confirm that:
|
||||
|
||||
- The JSON file name matches the SVG file name.
|
||||
- The file includes all required fields.
|
||||
- Contributors are GitHub usernames.
|
||||
- Use cases describe real interface usage.
|
||||
- Tags are lowercase and useful for search.
|
||||
- Categories are allowed by `icon.schema.json`.
|
||||
- Metadata is specific to the icon and its variants.
|
||||
- `pnpm run lint:json:icons` passes.
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
description: Learn the best practices for writing tags for Lucide icons.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { batteryLow, calendar, droplet, hospital, mail, mailSearch, search, type } from '~/.vitepress/data/iconNodes';
|
||||
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
|
||||
</script>
|
||||
|
||||
# Tag guide
|
||||
|
||||
This guide shows how to write useful search tags for Lucide icons.
|
||||
|
||||
Tags help people find icons by concept, synonym, context, or common wording.
|
||||
|
||||
## Use lowercase tags
|
||||
|
||||
Tags should always be lowercase.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="mail" :iconNode="mail" /> email
|
||||
Lowercase tags **match the style** used across the icon library.
|
||||
:::
|
||||
::: dont <LucideIcon name="mail" :iconNode="mail" /> Email
|
||||
Capitalized tags create **inconsistent metadata** without improving search.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Prefer short tags
|
||||
|
||||
Use one clear word when it is enough. Use multi-word tags when the phrase is clearer.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="search" :iconNode="search" /> search
|
||||
This is **short, common, and easy to find**.
|
||||
:::
|
||||
::: dont <LucideIcon name="search" :iconNode="search" /> searching through files & folders
|
||||
This reads like a **use case**, not a tag.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Use multi-word tags when needed
|
||||
|
||||
Some ideas are only clear as a phrase. But keep those phrases short.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="droplet" :iconNode="droplet" /> blood type
|
||||
The phrase is **clearer** than either word on its own.
|
||||
:::
|
||||
::: dont <LucideIcon name="droplet" :iconNode="droplet" /> blood type medical label
|
||||
This combines **several search concepts** into one long tag.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Add synonyms and common wording
|
||||
|
||||
Tags can include words people are likely to search for, even if they are not in the icon name.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="mail" :iconNode="mail" /> email
|
||||
This helps users find `mail` icons using a **common synonym**.
|
||||
:::
|
||||
::: dont <LucideIcon name="mail" :iconNode="mail" /> electronic postal message delivery
|
||||
This is unlikely search wording and **adds noise**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Do not repeat the icon name
|
||||
|
||||
The icon name is already searchable. Tags should add other ways to find it.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="mail-search" :iconNode="mailSearch" /> message filter
|
||||
This adds a **related concept** for `mail-search` without duplicating the name.
|
||||
:::
|
||||
::: dont <LucideIcon name="mail-search" :iconNode="mailSearch" /> mail search
|
||||
This **repeats the icon name** and does not improve discovery.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Avoid generic tags
|
||||
|
||||
Tags should describe the icon, not say that it is an icon.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="calendar" :iconNode="calendar" /> event
|
||||
This points to a **specific idea** users might search for.
|
||||
:::
|
||||
::: dont <LucideIcon name="calendar" :iconNode="calendar" /> icon
|
||||
**Every entry is an icon**, so this tag does not help anyone find the right one.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Keep tags relevant
|
||||
|
||||
Do not add loose terms just to make an icon appear in more searches.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="hospital" :iconNode="hospital" /> emergency room
|
||||
This is **relevant** for an icon that marks emergency room locations.
|
||||
:::
|
||||
::: dont <LucideIcon name="hospital" :iconNode="hospital" /> doctor nurse pharmacy
|
||||
These terms may be **nearby concepts**, but they do not all describe the same icon.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Match the specific variant
|
||||
|
||||
Related icons can share family terms, but variant tags should describe what is different.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="battery-low" :iconNode="batteryLow" /> depleted
|
||||
This distinguishes `battery-low` from **other battery icons**.
|
||||
:::
|
||||
::: dont <LucideIcon name="battery-low" :iconNode="batteryLow" /> charged
|
||||
This describes `battery-full` better than `battery-low`.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Use existing tags as a reference
|
||||
|
||||
Check similar icons before adding new tags. Reusing existing terms keeps search predictable.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="type" :iconNode="type" /> typography
|
||||
This matches existing text-formatting icons and keeps **related icons** together in search.
|
||||
:::
|
||||
::: dont <LucideIcon name="type" :iconNode="type" /> fontography
|
||||
Inventing a new term makes the icon harder to find and less consistent with related metadata.
|
||||
:::
|
||||
::::
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
description: Learn the best practices for writing use cases for Lucide icons.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { batteryLow, creditCard, crop, handshake, heading2, microchip, search, squareParking, triangleAlert, wifiOff } from '~/.vitepress/data/iconNodes';
|
||||
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
|
||||
</script>
|
||||
|
||||
# Use case guide
|
||||
|
||||
This guide shows how to write clear use cases for Lucide icons.
|
||||
|
||||
## Write from the interface's perspective
|
||||
|
||||
Describe what the interface tells the user. Do not write from the contributor's point of view.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="wifi-off" :iconNode="wifiOff" /> Indicating a device is offline or unreachable
|
||||
This describes the **role the icon plays in an interface**.
|
||||
:::
|
||||
::: dont <LucideIcon name="wifi-off" :iconNode="wifiOff" /> I need an icon for my offline device screen
|
||||
This explains the **contributor's situation**, not the icon's purpose.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Describe real usage, not the icon name
|
||||
|
||||
A use case should explain what the icon means in context. Do not repeat the icon name or only describe the drawing.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="microchip" :iconNode="microchip" /> Representing processors, chips, or embedded hardware
|
||||
This explains `microchip` **without repeating the name**.
|
||||
:::
|
||||
::: dont <LucideIcon name="microchip" :iconNode="microchip" /> it's a microchip icon
|
||||
This **duplicates the icon name** and does not explain where the icon would be used.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Add context when it clarifies meaning
|
||||
|
||||
Some icons have broad meanings. Add context when it makes the use case clearer.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="heading-2" :iconNode="heading2" /> Applying a level-2 heading in text editors
|
||||
The phrase explains both the **action and the product area**.
|
||||
:::
|
||||
::: dont <LucideIcon name="heading-2" :iconNode="heading2" /> Applying a heading
|
||||
This is less useful because it **omits the level and context**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Keep each use case focused
|
||||
|
||||
Each entry should cover one clear idea. Split different meanings into separate entries.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="square-parking" :iconNode="squareParking" /> Marking parking locations on maps
|
||||
This is **short, clear, and focused** on one interface function.
|
||||
:::
|
||||
::: dont <LucideIcon name="square-parking" :iconNode="squareParking" /> Marking parking, transport, maps, cars, garages, and places
|
||||
This reads like a **tag list** and **mixes several ideas**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Write variant-specific use cases
|
||||
|
||||
For related icons, describe what makes each variant different.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="battery-low" :iconNode="batteryLow" /> Indicating a low battery charge level
|
||||
This is specific to `battery-low` and **sets it apart** from other battery icons.
|
||||
:::
|
||||
::: dont <LucideIcon name="battery-low" :iconNode="batteryLow" /> Representing battery status
|
||||
This is **too generic** and could apply to **every battery variant**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Avoid pull request references
|
||||
|
||||
Use cases should make sense after the pull request is merged.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="handshake" :iconNode="handshake" /> Signifying a deal, agreement, or partnership
|
||||
This keeps the **useful meaning** without depending on **outside context**.
|
||||
:::
|
||||
::: dont <LucideIcon name="handshake" :iconNode="handshake" /> Same as above in #1234
|
||||
This **depends on a discussion** readers **may never see**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Keep entries concise
|
||||
|
||||
Use cases should usually be 4 to 12 words. Prefer a short phrase over a long explanation.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="search" :iconNode="search" /> Searching files by name or content
|
||||
This is **short enough** to scan and **specific enough** to understand.
|
||||
:::
|
||||
::: dont <LucideIcon name="search" :iconNode="search" /> This icon can be used when users want to search through all of their files and folders to find something
|
||||
This is **too long** and reads like **product copy instead of metadata**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Do not end with punctuation
|
||||
|
||||
Use cases are metadata phrases, not full sentences.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="credit-card" :iconNode="creditCard" /> Confirming a successful payment
|
||||
This matches the **phrase style** used across icon metadata.
|
||||
:::
|
||||
::: dont <LucideIcon name="credit-card" :iconNode="creditCard" /> Confirming a successful payment.
|
||||
The period adds **unnecessary punctuation** and makes entries inconsistent.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Avoid markdown and emoji
|
||||
|
||||
Use plain text only. Formatting belongs in documentation, not metadata values.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> Warning users about a destructive action
|
||||
This works in search, generated pages, and other places that use metadata.
|
||||
:::
|
||||
::: dont <LucideIcon name="triangle-alert" :iconNode="triangleAlert" /> <span>**Warning** users about a destructive action ⚠️</span>
|
||||
Markdown and emoji can **show up in generated UI** and make metadata **harder to reuse**.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Avoid implementation details
|
||||
|
||||
Use cases should describe meaning, not how the SVG was made.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="crop" :iconNode="crop" /> Representing cropped or trimmed content
|
||||
This explains the icon's **interface meaning**.
|
||||
:::
|
||||
::: dont <LucideIcon name="crop" :iconNode="crop" /> Showing a rectangle with two path cuts and adjusted Bezier handles
|
||||
This describes **construction details** that do not help people find or understand the icon.
|
||||
:::
|
||||
::::
|
||||
@@ -0,0 +1,238 @@
|
||||
---
|
||||
description: Learn how to choose clear and consistent names for Lucide icons.
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
arrowDown01,
|
||||
badgeCheck,
|
||||
badgePlus,
|
||||
ban,
|
||||
boneFracture,
|
||||
circleDashed,
|
||||
circleFadingArrowUp,
|
||||
circleUser,
|
||||
clock3,
|
||||
heartCrack,
|
||||
moonStar,
|
||||
notepadTextDashed,
|
||||
pencilRuler,
|
||||
rulerDimensionLine,
|
||||
save,
|
||||
sendHorizontal,
|
||||
squareDashed,
|
||||
sunSnow,
|
||||
userRound,
|
||||
} from '~/.vitepress/data/iconNodes';
|
||||
import LucideIcon from '~/.vitepress/theme/components/base/LucideIcon.vue';
|
||||
const pencilRulerHorizontal = [
|
||||
["path",{"d":"M10 16v-3","key":"1sz6hv"}],
|
||||
["path",{"d":"M14 16v-3","key":"1rh0ni"}],
|
||||
["path",{"d":"M16 2.999v5.98","key":"q5wnuh"}],
|
||||
["path",{"d":"M18 16v-3","key":"gc63g1"}],
|
||||
["path",{"d":"M19.005 2.994A3 3 0 0122 5.984a3 3 0 01-2.986 2.99l-11.777.017a2 2 0 01-.943-.25l-4.03-2.274a.5.5 0 010-.934l4.03-2.274a2 2 0 01.944-.247z","key":"1bk0ej"}],
|
||||
["path",{"d":"M6 16v-3","key":"h6yyjy"}],
|
||||
["rect",{"x":"2","y":"13","width":"20","height":"8","rx":"2","key":"ct2kk9"}]
|
||||
];
|
||||
const pencilRulerVertical = [
|
||||
["path",{"d":"M16 10h-3","key":"4p2xpj"}],
|
||||
["path",{"d":"M16 14h-3","key":"1xsqil"}],
|
||||
["path",{"d":"M16 18h-3","key":"12gup7"}],
|
||||
["path",{"d":"M16 6h-3","key":"1o5sfa"}],
|
||||
["path",{"d":"M2.995 19.005a1 1 0 105.98.01L8.99 7.236a2 2 0 00-.25-.943l-2.274-4.03a.5.5 0 00-.934 0L3.26 6.294a2 2 0 00-.248.944z","key":"rw57er"}],
|
||||
["path",{"d":"M3 16h5.979","key":"kkz6vb"}],
|
||||
["rect",{"x":"13","y":"2","width":"8","height":"20","rx":"2","key":"qzgro9"}]
|
||||
];
|
||||
</script>
|
||||
|
||||
# Naming conventions
|
||||
|
||||
Learn how to choose clear, consistent names for Lucide icons.
|
||||
|
||||
This page covers word order, modifiers, variants, related icons, and other rules that keep icon names predictable.
|
||||
|
||||
Use this when naming a new icon or reviewing a proposed name.
|
||||
|
||||
## 1. Use lowercase kebab-case
|
||||
|
||||
Icon names **must** use lowercase kebab-case.
|
||||
|
||||
:::: example
|
||||
::: do `arrow-up-0-1`
|
||||
Use lowercase kebab-case.
|
||||
:::
|
||||
::: dont `Arrow Up 0-1` or `arrowUp01`
|
||||
Avoid other naming schemes.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 2. Use American English
|
||||
|
||||
Icon names **must** use American English, not local variants.
|
||||
|
||||
:::: example
|
||||
::: do <span>`color`, `maximize`, `center`</span>
|
||||
:::
|
||||
::: dont <span>`colour`, `maximise`, `centre`</span>
|
||||
:::
|
||||
::::
|
||||
|
||||
## 3. Name icons for what they depict
|
||||
|
||||
Icon names **must** describe what the icon shows, not how someone might use it.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="save" :iconNode="save" /> `floppy-disk`
|
||||
The icon **shows** a floppy disk.
|
||||
:::
|
||||
::: dont <LucideIcon name="save" :iconNode="save" /> `save`
|
||||
Save is a use case.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="ban" :iconNode="ban" /> `circle-slash`
|
||||
The icon **shows** a circle with a slash across it.
|
||||
:::
|
||||
::: dont <LucideIcon name="ban" :iconNode="ban" /> `ban`
|
||||
Ban is an action.
|
||||
:::
|
||||
::::
|
||||
|
||||
::: tip
|
||||
An icon can mean different things in different products. Naming icons by appearance keeps names clear and independent from use cases.
|
||||
:::
|
||||
|
||||
## 4. Name related icons consistently
|
||||
|
||||
Icons that belong to the same group **must** use the `<group>-<variant>` naming scheme.
|
||||
|
||||
The group name comes first. The variant comes after it.
|
||||
|
||||
:::: example
|
||||
::: do <span><LucideIcon name="badge-plus" :iconNode="badgePlus" /> + <LucideIcon name="badge-check" :iconNode="badgeCheck" /></span> <span>`badge-plus` & `badge-check`</span>
|
||||
`badge` is the group.
|
||||
:::
|
||||
::: dont <span><LucideIcon name="badge-plus" :iconNode="badgePlus" /> + <LucideIcon name="badge-check" :iconNode="badgeCheck" /></span> <span>`plus-badge` & `check-badge`</span>
|
||||
`plus` and `check` are not the group.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 5. Describe alternate icons
|
||||
|
||||
Alternate versions of an icon **must** be named for what makes them visually different. Do **not** use numbers just to separate one version from another.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="send-horizontal" :iconNode="sendHorizontal" /> `send-horizontal`
|
||||
The icon depicts a horizontal "send" symbol.
|
||||
:::
|
||||
::: dont <LucideIcon name="send-horizontal" :iconNode="sendHorizontal" /> <span>`send-2` or `send-alt`</span>
|
||||
"send number 2" and "alternative send" are not clear names.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 6. Only use numerals when depicted
|
||||
|
||||
Icon names **must not** include numbers unless the icon shows the number.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="arrow-down-0-1" :iconNode="arrowDown01" /> `arrow-down-0-1`
|
||||
The arrow points from 0 to 1.
|
||||
:::
|
||||
::: dont <LucideIcon name="send-horizontal" :iconNode="sendHorizontal" /> `send-2`
|
||||
The icon does not show the number 2.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="clock-3" :iconNode="clock3" /> `clock-3`
|
||||
The hands point to 3 o'clock.
|
||||
:::
|
||||
::: dont <LucideIcon name="user-round" :iconNode="userRound" /> `user-3`
|
||||
The icon does not show the number 3.
|
||||
:::
|
||||
::::
|
||||
|
||||
Do not use numbers to separate similar icons.
|
||||
|
||||
## 7. Order elements by size
|
||||
|
||||
When an icon shows multiple elements of different sizes, order their names from largest to smallest.
|
||||
|
||||
For an icon containing a circle and a person:
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="moon-star" :iconNode="moonStar" /> `moon-star`
|
||||
The `moon` is larger than the `star`.
|
||||
:::
|
||||
::: dont <LucideIcon name="circle-user" :iconNode="circleUser" /> `person-circle`
|
||||
The `person` is **not** larger than the `circle`.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 8. Order equally sized elements by position
|
||||
|
||||
When an icon depicts multiple elements of roughly equal size, their names **must** be ordered according to their visual position.
|
||||
|
||||
If elements overlap, name them from front to back.
|
||||
|
||||
If they do not overlap, name them in English reading order: top to bottom, then left to right.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="pencil-ruler" :iconNode="pencilRuler" /> `pencil-ruler`
|
||||
The `pencil` is **in front** of the `ruler`, so it comes first.
|
||||
:::
|
||||
::: do <LucideIcon name="ruler-dimension-line" :iconNode="rulerDimensionLine" /> `ruler-dimension-line`
|
||||
The ruler is below the dimension line, but it is **larger**, so it comes first.
|
||||
:::
|
||||
::::
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="sun-snow" :iconNode="sunSnow" /> `sun-snow`
|
||||
The `sun` is **left of** the `snowflake`, so it comes first.
|
||||
:::
|
||||
::: dont <LucideIcon name="pencil-ruler-horizontal" :iconNode="pencilRulerHorizontal" /> `ruler-pencil`
|
||||
The `ruler` is **below** the `pencil`, so `pencil-ruler` is the correct name.
|
||||
:::
|
||||
::::
|
||||
|
||||
## 9. Place modifiers after the element they describe
|
||||
|
||||
Modifiers **must** come after the element they describe: `<element>-<modifier>`.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="heart-crack" :iconNode="heartCrack" /> `heart-broken`
|
||||
The icon shows a heart that is **cracked**.
|
||||
:::
|
||||
::: dont <LucideIcon name="bone-fracture" :iconNode="boneFracture" /> `broken-bone`
|
||||
The icon shows a bone that is **broken**, so name it `bone-broken`.
|
||||
:::
|
||||
::::
|
||||
|
||||
When an icon has multiple modified elements, each modifier **must** follow the element it describes.
|
||||
|
||||
:::: example
|
||||
::: do <LucideIcon name="circle-fading-arrow-up" :iconNode="circleFadingArrowUp" /> `circle-fading-arrow-up`
|
||||
The icon shows a **fading** circle with an arrow pointing **up** inside it.
|
||||
|
||||
`circle` comes first because it is larger than `arrow`. `fading` follows `circle` because it modifies the circle. `up` follows `arrow` because it modifies the arrow.
|
||||
:::
|
||||
::: dont <LucideIcon name="notepad-text-dashed" :iconNode="notepadTextDashed" /> `notepad-text-dashed`
|
||||
This icon shows a dashed notepad with text inside, so name it `notepad-dashed-text`.
|
||||
|
||||
`notepad` comes first because it is larger than `text`. `dashed` follows `notepad` because it modifies the notepad. `text` follows both because `dashed` does not modify `text`.
|
||||
:::
|
||||
::::
|
||||
|
||||
## Summary
|
||||
|
||||
When naming an icon:
|
||||
|
||||
1. **Describe what you see**, not what the icon could mean.
|
||||
2. **Use American English** and **lowercase kebab-case**.
|
||||
3. **Keep related icons together** with `<group>-<variant>`.
|
||||
4. **Describe what makes an alternate unique** instead of numbering it.
|
||||
5. **Only use numerals when they are depicted** in the icon.
|
||||
6. **Order elements from largest to smallest**.
|
||||
7. If elements are roughly the same size, order them **front to back**, or otherwise in **English reading order**.
|
||||
8. **Place modifiers after the element they describe**: `<element>-<modifier>`.
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
title: Illustrator Template Guide
|
||||
---
|
||||
|
||||
# Illustrator Template Guide
|
||||
|
||||
This Guide explains how to properly use the Adobe Illustrator Template for Lucide.
|
||||
|
||||
>Attention: Even though it is unlikely the template can be outdated or not 100% correct. Please check the Icon Design Guide before you start working with the template to ensure integrity with the Lucide icon pack.
|
||||
|
||||
## General Workflow
|
||||
|
||||
The Illustrator template is created following guidelines from the [Icon Design Guide](icon-design-guide.md).
|
||||
|
||||
**Workflow:**
|
||||
|
||||
1. Download and open the [Illustrator template](https://github.com/lucide-icons/lucide/blob/main/docs/public/templates/illustrator_template.ai).
|
||||
|
||||
2. You can now remove the content from the example logo layer ("Draw") and start creating.
|
||||
|
||||
3. Verify that you follow the [Icon Design Guidelines](icon-design-guide.md).
|
||||
|
||||
4. Before you export the file as an SVG make sure to check that you followed the guidelines and remove all unnecessary layers (especially "Padding" and "Grid").
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
After that, double check that the [code conventions and SVG global attributes](icon-design-guide.md#code-conventions) are correct.
|
||||
|
||||
7. Minify paths with [SVGOMG](https://jakearchibald.github.io/svgomg/).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user