mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-11 13:28:19 +00:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
457a2d1766 | ||
|
|
8679e10aaa | ||
|
|
e1ee65bc23 |
@@ -9,4 +9,3 @@ strikethrough
|
||||
touchpad
|
||||
ungroup
|
||||
toc
|
||||
sunlounger
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
lib
|
||||
tests
|
||||
node_modules
|
||||
.eslintrc.js
|
||||
docs/images
|
||||
docs/**/examples/
|
||||
packages/lucide-react/dynamicIconImports.js
|
||||
packages/angular/.angular
|
||||
@@ -0,0 +1,75 @@
|
||||
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',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
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
|
||||
@@ -13,19 +13,21 @@ body:
|
||||
description: Which Lucide packages are affected? You may select more than one.
|
||||
options:
|
||||
- label: lucide
|
||||
- label: lucide-react
|
||||
- label: '@lucide/vue'
|
||||
- label: '@lucide/svelte'
|
||||
- label: '@lucide/angular'
|
||||
- label: lucide-angular (old version)
|
||||
- label: '@lucide/angular (new version)'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-react-native
|
||||
- label: lucide-flutter
|
||||
- label: lucide-preact
|
||||
- label: lucide-react
|
||||
- label: lucide-react-native
|
||||
- label: lucide-solid
|
||||
- label: lucide-static
|
||||
- label: lucide-svelte (old version)
|
||||
- label: '@lucide/svelte (new version)'
|
||||
- label: lucide-vue
|
||||
- label: lucide-vue-next
|
||||
- label: lucide-astro
|
||||
- label: '@lucide/icons'
|
||||
- label: lucide-vue-next (deprecated)
|
||||
- label: lucide-svelte (deprecated)
|
||||
- label: lucide-angular (deprecated)
|
||||
- label: Figma plugin
|
||||
- label: source/main
|
||||
- label: other/not relevant
|
||||
@@ -36,7 +38,7 @@ body:
|
||||
attributes:
|
||||
label: Version
|
||||
description: What version of Lucide are you running?
|
||||
placeholder: e.g. 1.1.0
|
||||
placeholder: e.g. 0.289.1
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
|
||||
@@ -13,19 +13,21 @@ body:
|
||||
description: Which Lucide project do you wish this feature were added to? You may select more than one.
|
||||
options:
|
||||
- label: lucide
|
||||
- label: lucide-react
|
||||
- label: '@lucide/vue'
|
||||
- label: '@lucide/svelte'
|
||||
- label: '@lucide/angular'
|
||||
- label: lucide-angular (old version)
|
||||
- label: '@lucide/angular (new version)'
|
||||
- label: '@lucide/astro'
|
||||
- label: lucide-react-native
|
||||
- label: lucide-flutter
|
||||
- label: lucide-preact
|
||||
- label: lucide-react
|
||||
- label: lucide-react-native
|
||||
- label: lucide-solid
|
||||
- label: lucide-static
|
||||
- label: lucide-svelte (old version)
|
||||
- label: '@lucide/svelte (new version)'
|
||||
- label: lucide-vue
|
||||
- label: lucide-vue-next
|
||||
- label: lucide-astro
|
||||
- label: '@lucide/icons'
|
||||
- label: lucide-vue-next (deprecated)
|
||||
- label: lucide-svelte (deprecated)
|
||||
- label: lucide-angular (deprecated)
|
||||
- label: Figma plugin
|
||||
- label: all JS packages
|
||||
- label: site
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
## Reviewing PRs
|
||||
|
||||
When reviewing PRs, please respond in a friendly and constructive manner.
|
||||
|
||||
- First, thank the contributor for their contribution in an enthusiastic way.
|
||||
- If you have any suggestions for improvement, provide them clearly and concisely.
|
||||
- If you have any questions about the PR, ask them respectfully.
|
||||
- Let the contributor know that we have many PRs to review and that we appreciate their patience while we review their contribution.
|
||||
@@ -1,106 +0,0 @@
|
||||
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:
|
||||
- '*'
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
applyTo: "icons/*.json"
|
||||
---
|
||||
# JSON Metadata Descriptor
|
||||
|
||||
The schema for the JSON metadata descriptor can be found in `icon.schema.json`. It defines the required and optional properties for the JSON files that accompany each icon. The JSON metadata descriptor should be placed in the same directory as the SVG file of the icon and should have the same name as the SVG file, but with a `.json` extension. For example, if the SVG file is named `home.svg`, the JSON metadata descriptor should be named `home.json`.
|
||||
|
||||
## Contributors
|
||||
|
||||
The `contributors` property is a required array of GitHub usernames for the people who created or contributed to the icon. It is used to give credit to contributors and to track the icon's history. Add the PR author to the `contributors` array if they are not already listed and they have made a significant contribution to the icon's SVG.
|
||||
|
||||
## Tags
|
||||
|
||||
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 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
|
||||
|
||||
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"
|
||||
@@ -1,587 +0,0 @@
|
||||
---
|
||||
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 Specification](./specification) for exact dimensions and spacing requirements.
|
||||
|
||||
**Don't:**
|
||||
|
||||
> For more information, [click here](./specification).
|
||||
|
||||
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.
|
||||
+10
-7
@@ -30,11 +30,6 @@
|
||||
- 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:
|
||||
@@ -57,13 +52,15 @@
|
||||
💎 vue package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/vue/*'
|
||||
- 'packages/lucide-vue/*'
|
||||
- 'packages/lucide-vue-next/*'
|
||||
|
||||
# For changes in the Lucide angular package
|
||||
🅰️ angular package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/angular/*'
|
||||
- 'packages/lucide-angular/*'
|
||||
|
||||
# For changes in the Lucide preact package
|
||||
⚛️ preact package:
|
||||
@@ -75,7 +72,7 @@
|
||||
🧣 svelte package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/svelte/*'
|
||||
- 'packages/lucide-svelte/*'
|
||||
|
||||
# For changes in the Lucide solid package
|
||||
🪝 solid package:
|
||||
@@ -100,3 +97,9 @@
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-static/*'
|
||||
|
||||
# For changes in the Lucide flutter package
|
||||
🏹 flutter package:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- 'packages/lucide-flutter/*'
|
||||
|
||||
@@ -41,15 +41,13 @@ Common scopes: icons, docs, studio, site, dev
|
||||
|
||||
### Naming <!-- ONLY for new icons -->
|
||||
<!-- All of these requirements must be fulfilled. -->
|
||||
- [ ] I've read and followed the [naming conventions](https://lucide.dev/contribute/icon-design-guide#naming-conventions)
|
||||
- [ ] I've read and followed the [naming conventions](https://lucide.dev/guide/design/icon-design-guide#naming-conventions)
|
||||
- [ ] I've named icons by what they are rather than their use case.
|
||||
- [ ] I've provided meta JSON files in `icons/[iconName].json`.
|
||||
|
||||
### 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 read and followed the [icon design guidelines](https://lucide.dev/guide/design/icon-design-guide)
|
||||
- [ ] 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,16 +7,13 @@ on:
|
||||
- tools/build-icons/**
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -30,9 +27,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
+13
-46
@@ -23,51 +23,24 @@ on:
|
||||
- beta
|
||||
- alpha
|
||||
|
||||
permissions: {}
|
||||
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
|
||||
|
||||
jobs:
|
||||
check-dispatch-gate:
|
||||
if: github.repository == 'lucide-icons/lucide' &&
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
|
||||
- name: Check if latest tag was created today
|
||||
id: environment
|
||||
run: |
|
||||
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
|
||||
TAG_DATE=$(git log -1 --format=%cs "$LATEST_TAG")
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
if [ "$TAG_DATE" == "$TODAY" ]; then
|
||||
echo "::warning::A release tag already exists for today ($TODAY): $LATEST_TAG. Manual approval will be required to proceed."
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release Gated" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
create-release:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
needs: check-dispatch-gate
|
||||
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
|
||||
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)') && github.event_name != 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # Required to create the release and tag
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -102,7 +75,7 @@ jobs:
|
||||
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
@@ -113,19 +86,13 @@ 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.repository == 'lucide-icons/lucide' && github.event_name == 'workflow_dispatch'
|
||||
if: 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 }}
|
||||
|
||||
@@ -3,28 +3,18 @@ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Load stopwords from JSON & check issue title & body
|
||||
if: github.event_name == 'issues' && contains(github.event.issue.labels.*.name, '🙌 icon request')
|
||||
if: 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")
|
||||
@@ -37,17 +27,15 @@ 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.
|
||||
|
||||
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)
|
||||
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${URL_ENCODED_VALUE})
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
|
||||
|
||||
We’re always happy to help on [Discord](https://discord.gg/EH6nSts)."
|
||||
|
||||
@@ -56,55 +44,5 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
- 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
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -3,16 +3,13 @@ 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@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
- uses: actions/stale@v9
|
||||
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,22 +6,15 @@ 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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -49,7 +42,7 @@ jobs:
|
||||
id: pr-number
|
||||
|
||||
- name: Find Comment
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: pr-comment
|
||||
with:
|
||||
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||
@@ -57,7 +50,7 @@ jobs:
|
||||
body-includes: Added or changed icons
|
||||
|
||||
- name: Create or update comment
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
with:
|
||||
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
|
||||
issue-number: ${{ steps.pr-number.outputs.number }}
|
||||
|
||||
@@ -9,16 +9,13 @@ on:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,9 +29,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -2,15 +2,12 @@ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/labeler@v5
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
name: Linting PR code
|
||||
name: Linting PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
paths-ignore:
|
||||
- icons/*.svg
|
||||
|
||||
jobs:
|
||||
lint-code:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Linting PR title
|
||||
name: Linting PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -7,16 +7,12 @@ 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@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
- uses: amannn/action-semantic-pull-request@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -7,9 +7,6 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'icons/*'
|
||||
- 'lab/*'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
lint-filenames:
|
||||
@@ -19,54 +16,27 @@ jobs:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
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@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files: |
|
||||
icons/*
|
||||
lab/*
|
||||
files: icons/*
|
||||
|
||||
- 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v6
|
||||
- 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 '"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
name: Lucide Angular checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-angular/**
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-angular build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-angular test
|
||||
@@ -11,16 +11,13 @@ on:
|
||||
- tools/build-icons/**
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
@@ -34,9 +31,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v3
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: 'pnpm'
|
||||
|
||||
@@ -9,16 +9,13 @@ on:
|
||||
- tools/build-font/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-font:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -26,14 +23,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Execute unit tests for build-font
|
||||
run: pnpm test:font
|
||||
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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,16 +12,13 @@ on:
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-preact:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,16 +12,13 @@ on:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -35,9 +32,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -7,25 +7,19 @@ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -39,9 +33,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -51,22 +45,3 @@ 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,16 +9,13 @@ on:
|
||||
- packages/shared/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,16 +12,13 @@ on:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -35,9 +32,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -7,16 +7,13 @@ on:
|
||||
- tools/build-icons/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lucide-static:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -12,16 +12,13 @@ on:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -35,9 +32,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -51,9 +48,9 @@ jobs:
|
||||
typecheck:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Lucide Svelte checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-svelte/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-svelte build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-svelte test
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Lucide Vue Next checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-vue-next/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- pnpm-lock.yaml
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter lucide-vue-next build
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-vue-next test
|
||||
@@ -6,22 +6,19 @@ on:
|
||||
- next
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/vue/**
|
||||
- packages/lucide-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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -35,9 +32,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -11,16 +11,13 @@ on:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -34,9 +31,9 @@ jobs:
|
||||
test:
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
@@ -5,30 +5,27 @@ 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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Get changed files
|
||||
id: changed-files
|
||||
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
uses: tj-actions/changed-files@v46
|
||||
with:
|
||||
files: icons/*.svg
|
||||
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@v6
|
||||
- name: Install svgson for code preview (safer and faster than installing all deps)
|
||||
run: npm install svgson@5.3.1 --force
|
||||
|
||||
@@ -43,7 +40,7 @@ jobs:
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
|
||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pr_number
|
||||
path: pr/
|
||||
|
||||
+7
-10
@@ -1,16 +1,13 @@
|
||||
name: Pull request metadata suggestions
|
||||
name: Pull request tags suggestions
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
paths:
|
||||
- 'icons/*.json'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pull-request-metadata-suggestions:
|
||||
name: Pull Request Metadata Suggestions
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
pull-request-tags-suggestions:
|
||||
name: Pull Request Tags Suggestions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -19,11 +16,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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: lucide-icons/lucide
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -32,7 +29,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate comment markup
|
||||
run: node ./scripts/suggestMetaData.mts
|
||||
run: node ./scripts/suggestTags.mts
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
PULL_REQUEST_NUMBER: ${{ github.event.number }}
|
||||
@@ -1,146 +0,0 @@
|
||||
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,7 +16,12 @@ on:
|
||||
default: 'latest'
|
||||
type: string
|
||||
|
||||
permissions: {}
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
@@ -24,6 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
||||
|
||||
steps:
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
@@ -57,9 +63,9 @@ jobs:
|
||||
'@lucide/vue',
|
||||
]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -68,7 +74,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter ${{ matrix.package }} --fail-if-no-match --no-git-tag-version
|
||||
run: pnpm --fail-if-no-match --filter ${{ matrix.package }} version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter ${{ matrix.package }} build
|
||||
@@ -90,14 +96,10 @@ jobs:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- 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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -106,7 +108,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter lucide-static --no-git-tag-version
|
||||
run: pnpm --filter lucide-static version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
|
||||
- name: Move Font
|
||||
run: cp -r lucide-font packages/lucide-static/font
|
||||
@@ -123,12 +125,10 @@ jobs:
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare
|
||||
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
|
||||
- uses: actions/checkout@v6
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
@@ -136,16 +136,13 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Execute unit tests for build-font
|
||||
run: pnpm test:font
|
||||
|
||||
- name: Create font in ./lucide-font
|
||||
run: pnpm build:font --saveCodePoints
|
||||
env:
|
||||
BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }}
|
||||
|
||||
- name: 'Upload to Artifacts'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
@@ -155,21 +152,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prepare, lucide-font]
|
||||
permissions:
|
||||
contents: write # Required to upload the release assets
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: lucide-font
|
||||
path: lucide-font
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/download-artifact@v4
|
||||
- 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@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ needs.prepare.outputs.VERSION }}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
name: Weekly Repo Report
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# 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@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Install Copilot CLI
|
||||
run: npm install -g @github/copilot
|
||||
|
||||
- name: Run the prompt
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
copilot -p "${{ vars.REPO_REPORT_PROMPT }} Write the details to summary.md" --allow-tool='shell(git:*)' --allow-tool=write --no-ask-user
|
||||
cat summary.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Post report to Discord (private channel)
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.LUCIDE_DISCORD_WEBHOOK }}
|
||||
run: |
|
||||
jq -Rs '{
|
||||
embeds: [{
|
||||
title: "📊 Weekly Repo Report",
|
||||
description: (.[0:4096]),
|
||||
color: 5814783
|
||||
}]
|
||||
}' summary.md > payload.json
|
||||
curl -sS --fail -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @payload.json \
|
||||
"$DISCORD_WEBHOOK_URL"
|
||||
@@ -1,27 +1,25 @@
|
||||
name: Request Review
|
||||
name: 'Request Review'
|
||||
on:
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
paths:
|
||||
- icons/*.svg
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
request-review:
|
||||
if: github.repository == 'lucide-icons/lucide' && github.event.pull_request.draft == false
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@v6
|
||||
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@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v47.0.6
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: icons/*.svg
|
||||
- run: |
|
||||
|
||||
+2
-16
@@ -3,10 +3,9 @@
|
||||
.next
|
||||
.obsidian
|
||||
.now
|
||||
.cache
|
||||
.idea
|
||||
.env
|
||||
node_modules
|
||||
.pnpm-store
|
||||
dist
|
||||
build
|
||||
sandbox
|
||||
@@ -14,10 +13,6 @@ stash
|
||||
coverage
|
||||
stats
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
integrations/**/.react-router
|
||||
integrations/**/.vitest-attachments
|
||||
integrations/**/__screenshots__
|
||||
outlined
|
||||
lucide-font
|
||||
packages/**/src/icons/*.js
|
||||
@@ -28,17 +23,12 @@ 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
|
||||
packages/**/dynamicIconImports.mjs
|
||||
packages/**/dynamicIconImports.d.ts
|
||||
packages/**/dynamicIconImports.d.mts
|
||||
packages/**/dynamicIconImports.js.map
|
||||
packages/**/dynamicIconImports.mjs.map
|
||||
packages/**/dynamic.d.ts
|
||||
packages/**/dynamic.d.mts
|
||||
packages/**/dynamic.mjs.map
|
||||
@@ -47,7 +37,6 @@ packages/**/LICENSE
|
||||
categories.json
|
||||
tags.json
|
||||
.vercel
|
||||
package-lock.json
|
||||
|
||||
# docs
|
||||
docs/.vitepress/cache
|
||||
@@ -61,11 +50,8 @@ docs/.vitepress/data/categoriesData.json
|
||||
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,2 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint-staged
|
||||
pnpm checkIcons
|
||||
|
||||
+4
-12
@@ -7,8 +7,8 @@ docs/.vitepress/cache
|
||||
docs/.vitepress/data
|
||||
docs/.nitro
|
||||
|
||||
# @lucide/angular
|
||||
packages/angular/.angular
|
||||
# lucide-angular
|
||||
packages/lucide-angular/.angular/cache
|
||||
|
||||
# lucide-static
|
||||
packages/lucide-static/icons
|
||||
@@ -17,15 +17,7 @@ packages/lucide-static/sprite.svg
|
||||
packages/lucide-static/tags.json
|
||||
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
|
||||
packages/lucide-svelte/src/icons/*.svelte
|
||||
packages/lucide-svelte/.svelte-kit
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"cSpell.words": ["devs", "linecap", "linejoin", "preact", "Preact"],
|
||||
"cSpell.words": ["devs", "preact", "Preact"],
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": ["javascript", "svg"],
|
||||
"svg.preview.background": "editor"
|
||||
|
||||
@@ -39,7 +39,7 @@ This means adding them could:
|
||||
Lucide is all about **visual consistency**.
|
||||
|
||||
Adding brand logos would:
|
||||
- Break [our own design rules](https://lucide.dev/contribute/icon-design-guide#icon-design-principles) for shapes, proportions, and stroke.
|
||||
- Break [our own design rules](https://lucide.dev/guide/design/icon-design-guide#icon-design-principles) for shapes, proportions, and stroke.
|
||||
- Mix two fundamentally different categories of graphics (pictograms vs. corporate logos).
|
||||
- Create a library where a subset of icons will always look "out of place".
|
||||
|
||||
|
||||
+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/?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.
|
||||
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.
|
||||
|
||||
#### Editor guides
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-ISC-green" alt="license"></a>
|
||||
<a href="https://github.com/lucide-icons/lucide/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/lucide" alt="license"></a>
|
||||
<a href="https://www.figma.com/community/plugin/939567362549682242/Lucide-Icons"><img src="https://img.shields.io/badge/Figma-F24E1E?logo=figma&logoColor=white" alt="figma installs"></a>
|
||||
<a href="https://github.com/lucide-icons/lucide/actions/workflows/ci.yml"><img src="https://github.com/lucide-icons/lucide/actions/workflows/ci.yml/badge.svg" alt="build status"></a>
|
||||
<a href="https://github.com/lucide-icons/lucide/actions/workflows/release.yml"><img src="https://github.com/lucide-icons/lucide/actions/workflows/release.yml/badge.svg" alt="build status"></a>
|
||||
<a href="https://discord.gg/EH6nSts"><img src="https://img.shields.io/discord/723074157486800936?label=chat&logo=discord&logoColor=%23ffffff&colorB=%237289DA" alt="discord chat"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
@@ -26,22 +26,22 @@
|
||||
|
||||
# Lucide
|
||||
|
||||
Lucide is an open-source icon library that provides 1600+ vector (svg) files for displaying icons and symbols in digital and non-digital projects. The library aims to make it easier for designers and developers to incorporate icons into their projects by providing several official [packages](https://lucide.dev/packages) to make it easier to use these icons in your project.
|
||||
Lucide is an open-source icon library that provides 1000+ vector (svg) files for displaying icons and symbols in digital and non-digital projects. The library aims to make it easier for designers and developers to incorporate icons into their projects by providing several official [packages](https://lucide.dev/packages) to make it easier to use these icons in your project.
|
||||
|
||||
## Packages
|
||||
|
||||
| Logo | Package | Version | Downloads | Links |
|
||||
| ---- | ------- | ------- | --------- | ----- |
|
||||
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | **`lucide`** | [](https://www.npmjs.com/package/lucide) |  | [Docs](https://lucide.dev/guide/lucide) · [Source](./packages/lucide) |
|
||||
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | **`lucide-react`** | [](https://www.npmjs.com/package/lucide-react) |  | [Docs](https://lucide.dev/guide/react) · [Source](./packages/lucide-react) |
|
||||
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | **`@lucide/vue`** | [](https://www.npmjs.com/package/@lucide/vue) |  | [Docs](https://lucide.dev/guide/vue) · [Source](./packages/vue) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`@lucide/svelte`** | [](https://www.npmjs.com/package/@lucide/svelte) |  | [Docs](https://lucide.dev/guide/svelte) · [Source](./packages/svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | **`lucide-solid`** | [](https://www.npmjs.com/package/lucide-solid) |  | [Docs](https://lucide.dev/guide/solid) · [Source](./packages/lucide-solid) |
|
||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/preact) · [Source](./packages/lucide-preact) |
|
||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/react-native) · [Source](./packages/lucide-react-native) |
|
||||
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | **`@lucide/angular`** | [](https://www.npmjs.com/package/@lucide/angular) |  | [Docs](https://lucide.dev/guide/angular) · [Source](./packages/angular) |
|
||||
| <img src="https://lucide.dev/framework-logos/astro.svg" alt="Astro logo" width="48"> | **`@lucide/astro`** | [](https://www.npmjs.com/package/@lucide/astro) |  | [Docs](https://lucide.dev/guide/astro) · [Source](./packages/astro) |
|
||||
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | **`lucide-static`** | [](https://www.npmjs.com/package/lucide-static) |  | [Docs](https://lucide.dev/guide/static) · [Source](./packages/lucide-static) |
|
||||
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | **`lucide`** | [](https://www.npmjs.com/package/lucide) |  | [Docs](https://lucide.dev/guide/packages/lucide) · [Source](./packages/lucide) |
|
||||
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | **`lucide-react`** | [](https://www.npmjs.com/package/lucide-react) |  | [Docs](https://lucide.dev/guide/packages/lucide-react) · [Source](./packages/lucide-react) |
|
||||
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | **`lucide-vue-next`** | [](https://www.npmjs.com/package/lucide-vue-next) |  | [Docs](https://lucide.dev/guide/packages/lucide-vue-next) · [Source](./packages/lucide-vue-next) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`lucide-svelte`** | [](https://www.npmjs.com/package/lucide-svelte) |  | [Docs](https://lucide.dev/guide/packages/lucide-svelte) · [Source](./packages/lucide-svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | **`lucide-solid`** | [](https://www.npmjs.com/package/lucide-solid) |  | [Docs](https://lucide.dev/guide/packages/lucide-solid) · [Source](./packages/lucide-solid) |
|
||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/packages/lucide-preact) · [Source](./packages/lucide-preact) |
|
||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/packages/lucide-react-native) · [Source](./packages/lucide-react-native) |
|
||||
| <img src="https://lucide.dev/framework-logos/angular.svg" alt="Angular logo" width="48"> | **`lucide-angular`** | [](https://www.npmjs.com/package/lucide-angular) |  | [Docs](https://lucide.dev/guide/packages/lucide-angular) · [Source](./packages/lucide-angular) |
|
||||
| <img src="https://lucide.dev/framework-logos/astro.svg" alt="Astro logo" width="48"> | **`@lucide/astro`** | [](https://www.npmjs.com/package/@lucide/astro) |  | [Docs](https://lucide.dev/guide/packages/lucide-astro) · [Source](./packages/astro) |
|
||||
| <img src="https://lucide.dev/framework-logos/svg.svg" alt="SVG logo" width="48"> | **`lucide-static`** | [](https://www.npmjs.com/package/lucide-static) |  | [Docs](https://lucide.dev/guide/packages/lucide-static) · [Source](./packages/lucide-static) |
|
||||
|
||||
### Figma
|
||||
|
||||
@@ -80,8 +80,6 @@ Thank you to all the people who contributed to Lucide!
|
||||
<img src="https://opencollective.com/lucide-icons/contributors.svg?width=800" />
|
||||
</a>
|
||||
|
||||
[//]: <> (Sponsors)
|
||||
|
||||
## Sponsors
|
||||
|
||||
<a href="https://vercel.com?utm_source=lucide&utm_campaign=oss">
|
||||
@@ -90,10 +88,7 @@ Thank you to all the people who contributed to Lucide!
|
||||
|
||||
<a href="https://www.digitalocean.com/?refcode=b0877a2caebd&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"><img src="docs/public/digitalocean.svg" width="200" alt="DigitalOcean Referral Badge" /></a>
|
||||
|
||||
### Hero backers 🦸
|
||||
|
||||
<a href="https://zephyr-cloud.io/"><img src="docs/public/sponsors/zephyr-cloud.svg" width="180" alt="Zephyr Cloud – From idea to prod: fast micro-frontend delivery!" /></a>
|
||||
|
||||
[//]: <> (Open Collective backers)
|
||||
### Awesome backers 🍺
|
||||
|
||||
<a href="https://github.com/pdfme/pdfme"><img src="docs/public/sponsors/pdfme.svg" width="180" alt="pdfme – Open-source PDF generation library built with TypeScript and React." /></a>
|
||||
|
||||
@@ -54,8 +54,10 @@
|
||||
"jira": "Jira",
|
||||
"kotlin": "Kotlin",
|
||||
"kubernetes": "Kubernetes",
|
||||
"less": "Less",
|
||||
"leetcode": "LeetCode",
|
||||
"leet-code": "LeetCode",
|
||||
"line": "LINE",
|
||||
"linkedin": "LinkedIn",
|
||||
"lua": "Lua",
|
||||
"mariadb": "MariaDB",
|
||||
@@ -68,7 +70,7 @@
|
||||
"nestjs": "NestJS",
|
||||
"netflix": "Netflix",
|
||||
"netlify": "Netlify",
|
||||
"nextjs": "Next.js",
|
||||
"next": "Next.js",
|
||||
"nodejs": "Node.js",
|
||||
"notion": "Notion",
|
||||
"nostr": "Nostr",
|
||||
@@ -104,8 +106,7 @@
|
||||
"shopify": "Shopify",
|
||||
"skype": "Skype",
|
||||
"slack": "Slack",
|
||||
"snapchat": "Snapchat",
|
||||
"solidjs": "SolidJS",
|
||||
"solid": "SolidJS",
|
||||
"soundcloud": "SoundCloud",
|
||||
"spotify": "Spotify",
|
||||
"sqlite": "SQLite",
|
||||
@@ -144,5 +145,6 @@
|
||||
"xbox": "Xbox",
|
||||
"yarn": "Yarn",
|
||||
"youtube": "YouTube",
|
||||
"zig": "Zig"
|
||||
"zig": "Zig",
|
||||
"zoom": "Zoom"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Emoji",
|
||||
"icon": "face-slightly-smiling"
|
||||
"icon": "smile"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Navigation & Places",
|
||||
"title": "Navigation, Maps, and POIs",
|
||||
"icon": "compass"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export default eventHandler(async (event) => {
|
||||
const name = params.data.split('/').at(-3);
|
||||
const iconSizeString = params.data.split('/').at(-2);
|
||||
const svgData = params.data.split('/').at(-1);
|
||||
const iconSize = Math.min(parseInt(iconSizeString, 10) || imageSize, 256);
|
||||
const iconSize = parseInt(iconSizeString, 10);
|
||||
const data = svgData.slice(0, -4);
|
||||
|
||||
const prevSvg = iconNodes[name]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
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,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
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;
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
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;
|
||||
}, {});
|
||||
});
|
||||
+97
-17
@@ -1,15 +1,15 @@
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
import { defineConfig, UserConfig } from 'vitepress';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons';
|
||||
import sidebar from './sidebar';
|
||||
import snackPlayer from './markdown/snackPlayer';
|
||||
import sandpackPlugin from './markdown/sandpack';
|
||||
import examplePlugin from './markdown/example';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { resourcesSidebar } from './sidebar/resources';
|
||||
import getStructuredData from './getStructuredData';
|
||||
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
|
||||
import llmstxt from 'vitepress-plugin-llms';
|
||||
import { transformPageData } from './transformPageData';
|
||||
import getHeadConfig from './getHeadConfig';
|
||||
import getHead from './getHead';
|
||||
|
||||
const defaultSandpackCSS = await readFile(
|
||||
fileURLToPath(new URL('./theme/sandpack-default.css', import.meta.url)),
|
||||
@@ -32,7 +32,6 @@ export default defineConfig({
|
||||
config(md) {
|
||||
md.use(groupIconMdPlugin);
|
||||
md.use(snackPlayer);
|
||||
md.use(examplePlugin);
|
||||
md.use(sandpackPlugin, {
|
||||
defaultFiles: {
|
||||
'/styles.css': {
|
||||
@@ -58,12 +57,6 @@ export default defineConfig({
|
||||
new URL('./theme/components/overrides/VPFooter.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: /^.*\/VPCarbonAds\.vue$/,
|
||||
replacement: fileURLToPath(
|
||||
new URL('./theme/components/overrides/VPCarbonAds.vue', import.meta.url),
|
||||
),
|
||||
},
|
||||
{
|
||||
find: '~/.vitepress',
|
||||
replacement: fileURLToPath(new URL('./', import.meta.url)),
|
||||
@@ -81,11 +74,99 @@ export default defineConfig({
|
||||
'brand-logo-statement.md',
|
||||
'icons/**', // Not working, need investigation
|
||||
],
|
||||
}) as unknown as UserConfig['vite']['plugins'][0],
|
||||
}),
|
||||
],
|
||||
},
|
||||
head: getHeadConfig({ title, description, socialTitle }),
|
||||
transformPageData,
|
||||
head: getHead({ title, socialTitle, description }),
|
||||
async transformPageData(pageData) {
|
||||
pageData.frontmatter.head ??= [];
|
||||
if (
|
||||
pageData.relativePath.startsWith('icons/') &&
|
||||
!pageData.relativePath.startsWith('icons/lab/') &&
|
||||
pageData.params?.name
|
||||
) {
|
||||
const iconName = pageData.params.name;
|
||||
pageData.title = `${iconName} icon details`;
|
||||
|
||||
const taggedAs = pageData.params?.tags?.length
|
||||
? `Tagged as: ${pageData.params.tags.join(', ')}.`
|
||||
: '';
|
||||
const categorizedIn = pageData.params?.category?.length
|
||||
? `Categorized in: ${pageData.params.category.join(', ')}.`
|
||||
: '';
|
||||
|
||||
pageData.description =
|
||||
`Details and related icons for ${iconName} icon. ${taggedAs} ${categorizedIn}`.trim();
|
||||
|
||||
const structuredData = await getStructuredData(iconName, pageData);
|
||||
|
||||
const ogPath = await createIconOGImage(iconName, pageData.params?.tags || []);
|
||||
|
||||
if (ogPath) {
|
||||
const content = `https://lucide.dev${ogPath}`;
|
||||
pageData.frontmatter.head.push(
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: pageData.title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: pageData.description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pageData.frontmatter.head.push([
|
||||
'script',
|
||||
{ type: 'application/ld+json' },
|
||||
JSON.stringify(structuredData),
|
||||
]);
|
||||
}
|
||||
|
||||
if (pageData.relativePath.startsWith('guide/')) {
|
||||
const ogPath = await createGeneralOGImage(pageData);
|
||||
|
||||
if (ogPath) {
|
||||
const content = `https://lucide.dev${ogPath}`;
|
||||
pageData.frontmatter.head.push(
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content,
|
||||
},
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
logo: {
|
||||
light: '/logo.light.svg',
|
||||
@@ -98,12 +179,11 @@ export default defineConfig({
|
||||
text: 'Resources',
|
||||
items: [
|
||||
...resourcesSidebar[0].items,
|
||||
{ text: 'Contributing icons', link: '/contribute/icons/' },
|
||||
{ text: 'Design icons', link: '/contribute/icon-design-guide' },
|
||||
],
|
||||
},
|
||||
{ text: 'Packages', link: '/packages' },
|
||||
{ text: 'Showcase', link: '/showcase' },
|
||||
{ text: 'Merch', link: 'https://merch.lucide.dev/' },
|
||||
],
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
@@ -112,7 +192,7 @@ export default defineConfig({
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Icons`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"title": "Navigation & Places"
|
||||
"title": "Navigation, Maps, and POIs"
|
||||
},
|
||||
{
|
||||
"name": "notifications",
|
||||
|
||||
@@ -46,5 +46,21 @@
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -50,21 +50,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"@lucide/svelte": {
|
||||
"lucide-svelte": {
|
||||
"order": 3,
|
||||
"icon": "svelte",
|
||||
"docsAlias": "svelte",
|
||||
"packageDirname": "svelte",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/@lucide/svelte",
|
||||
"href": "https://www.npmjs.com/package/@lucide/svelte"
|
||||
"src": "https://img.shields.io/npm/v/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/@lucide/svelte",
|
||||
"href": "https://www.npmjs.com/package/@lucide/svelte"
|
||||
"src": "https://img.shields.io/npm/dw/lucide-svelte",
|
||||
"href": "https://www.npmjs.com/package/lucide-svelte"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -120,6 +119,24 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-angular": {
|
||||
"order": 7,
|
||||
"icon": "angular-old",
|
||||
"description": "Our legacy Angular package with a module based API.",
|
||||
"documentation": "https://v0.lucide.dev/guide/packages/lucide-angular",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/v/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
},
|
||||
{
|
||||
"alt": "npm",
|
||||
"src": "https://img.shields.io/npm/dw/lucide-angular",
|
||||
"href": "https://www.npmjs.com/package/lucide-angular"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lucide-preact": {
|
||||
"order": 8,
|
||||
"icon": "preact",
|
||||
@@ -192,4 +209,4 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,24 +209,5 @@
|
||||
],
|
||||
"source": "https://github.com/shx08/strapi-lucide-icons",
|
||||
"documentation": "https://github.com/shx08/strapi-lucide-icons/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"name": "lucide-lit",
|
||||
"description": "Implementation of Lucide icons as Lit web components.",
|
||||
"icon": "/framework-logos/lit.svg",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "Latest Stable Version",
|
||||
"src": "https://img.shields.io/npm/v/@gyeonghokim/lucide-lit",
|
||||
"href": "https://www.npmjs.com/package/@gyeonghokim/lucide-lit"
|
||||
},
|
||||
{
|
||||
"alt": "Total Downloads",
|
||||
"src": "https://img.shields.io/npm/dw/@gyeonghokim/lucide-lit",
|
||||
"href": "https://www.npmjs.com/package/@gyeonghokim/lucide-lit"
|
||||
}
|
||||
],
|
||||
"source": "https://github.com/GyeongHoKim/lucide-lit",
|
||||
"documentation": "https://github.com/GyeongHoKim/lucide-lit/blob/main/README.md"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { HeadConfig } from "vitepress"
|
||||
|
||||
interface GetHeadOptions {
|
||||
title: string;
|
||||
socialTitle: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const brandColor = '#f56565'
|
||||
|
||||
const getHead = ({ title, socialTitle, description }: GetHeadOptions): HeadConfig[] => [
|
||||
// Favicons and meta tags
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'robots',
|
||||
content: 'index,follow',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
name: 'theme-color',
|
||||
content: brandColor,
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'shortcut icon',
|
||||
href: '/favicon.ico',
|
||||
sizes: 'any',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
href: '/favicon.svg',
|
||||
type: 'image/svg+xml',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
href: '/apple-touch-icon.png',
|
||||
sizes: '180x180',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/favicon-32x32.png',
|
||||
sizes: '32x32',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/favicon-16x16.png',
|
||||
sizes: '16x16',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'mask-icon',
|
||||
href: '/safari-pinned-tab.svg',
|
||||
color: brandColor,
|
||||
},
|
||||
],
|
||||
// Analytics
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'preconnect',
|
||||
href: 'https://analytics.lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
// Open Graph and Twitter Card meta tags
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:locale',
|
||||
content: 'en_US',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:width',
|
||||
content: '1200',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:height',
|
||||
content: '630',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:type',
|
||||
content: 'image/png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:alt',
|
||||
content: 'Lucide icon library preview image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:site',
|
||||
content: '@lucide_icons',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
export default getHead
|
||||
@@ -1,127 +0,0 @@
|
||||
import { HeadConfig } from 'vitepress';
|
||||
|
||||
const getHeadConfig = ({
|
||||
title,
|
||||
description,
|
||||
socialTitle,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
socialTitle?: string;
|
||||
}): HeadConfig[] => [
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'preconnect',
|
||||
href: 'https://analytics.lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://analytics.lucide.dev/js/script.js',
|
||||
'data-domain': 'lucide.dev',
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:locale',
|
||||
content: 'en_US',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:site_name',
|
||||
content: title,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:url',
|
||||
content: 'https://lucide.dev',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:width',
|
||||
content: '1200',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:height',
|
||||
content: '630',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'og:image:type',
|
||||
content: 'image/png',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:title',
|
||||
content: socialTitle,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
property: 'twitter:image',
|
||||
content: 'https://lucide.dev/og.png',
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export default getHeadConfig;
|
||||
@@ -29,8 +29,12 @@ export default async function getStructuredData(iconName: string, pageData: Page
|
||||
name: iconName,
|
||||
// TODO: Add support for description extraction from icon metadata
|
||||
// description: 'An ...SVG icon from the Lucide icon set.',
|
||||
contentUrl: `https://lucide.dev/api/icons/${iconName}}?width=24&height=24&background=white`,
|
||||
thumbnailUrl: `https://lucide.dev/api/icons/${iconName}}?width=256&height=256&background=white`,
|
||||
contentUrl: `https://lucide.dev/api/icons/${iconName}?width=24&height=24&background=white`,
|
||||
thumbnailUrl: `https://lucide.dev/api/icons/${iconName}?width=256&height=256&background=white`,
|
||||
acquireLicensePage: 'https://lucide.dev/license',
|
||||
license: 'https://lucide.dev/license',
|
||||
copyrightNotice: 'Copyright (c) 2026 Lucide Icons and Contributors',
|
||||
creditText: 'Lucide Icons',
|
||||
encodingFormat: 'image/svg+xml',
|
||||
keywords: pageData.params?.tags,
|
||||
width: 24,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
interface BackdropProps {
|
||||
src: string;
|
||||
|
||||
@@ -3,6 +3,7 @@ import pathToPoints from './path-to-points';
|
||||
import { Path, PathProps } from './types';
|
||||
|
||||
export const GapViolationHighlight = ({
|
||||
radius,
|
||||
stroke,
|
||||
strokeWidth,
|
||||
strokeOpacity,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } 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',
|
||||
never
|
||||
any
|
||||
>) => {
|
||||
return (
|
||||
<g
|
||||
@@ -361,12 +361,12 @@ const Radii = ({
|
||||
const Handles = ({
|
||||
paths,
|
||||
...props
|
||||
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', never>) => (
|
||||
}: { paths: Path[] } & PathProps<'strokeWidth' | 'stroke' | 'strokeOpacity', any>) => (
|
||||
<g
|
||||
className="svg-preview-handles-group"
|
||||
{...props}
|
||||
>
|
||||
{paths.map(({ prev, next, cp1, cp2 }, i) => (
|
||||
{paths.map(({ c, prev, next, cp1, cp2 }, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{cp1 && <path d={`M${prev.x} ${prev.y} ${cp1.x} ${cp1.y}`} />}
|
||||
{cp1 && (
|
||||
@@ -459,20 +459,7 @@ const SvgPreview = (
|
||||
/>
|
||||
<ColoredPath
|
||||
paths={paths}
|
||||
colors={[
|
||||
'#1982c4',
|
||||
'#4267AC',
|
||||
'#6a4c93',
|
||||
'#B55379',
|
||||
'#FF595E',
|
||||
'#FF7655',
|
||||
'#ff924c',
|
||||
'#FFAE43',
|
||||
'#ffca3a',
|
||||
'#C5CA30',
|
||||
'#8ac926',
|
||||
'#52A675',
|
||||
]}
|
||||
colors={['#dfdfd6']}
|
||||
/>
|
||||
<Radii
|
||||
paths={paths}
|
||||
|
||||
@@ -12,9 +12,7 @@ function pathToPoints({ d, prev, next }: Path, interval = 1) {
|
||||
points.push(commander.getPointAtLength(i));
|
||||
}
|
||||
points.push(next);
|
||||
} catch (err) {
|
||||
console.warn(`Failed to convert path to points for d="${d}": ${err}`);
|
||||
}
|
||||
} catch (err) {}
|
||||
return points;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export const getCommands = (src: string) =>
|
||||
.flatMap(({ d, name }, idx) =>
|
||||
new SVGPathData(d)
|
||||
.toAbs()
|
||||
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
|
||||
// @ts-ignore
|
||||
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
|
||||
);
|
||||
|
||||
@@ -242,7 +242,7 @@ const getPaths = (src: string) => {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// @ts-expect-error every command type is handled above, so `c` is not `never` here
|
||||
// @ts-ignore
|
||||
assertNever(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@ const getIconCodes = (): CodeExampleType => {
|
||||
title: 'Vanilla',
|
||||
code: `\
|
||||
<script>
|
||||
import { createIcons, $PascalCase } from 'lucide';
|
||||
import { createIcons, $CamelCase } from 'lucide';
|
||||
|
||||
createIcons({
|
||||
icons: {
|
||||
$PascalCase
|
||||
$CamelCase
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -119,7 +119,8 @@ 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,7 +2,8 @@ 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,9 +9,11 @@ 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);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
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 }];
|
||||
|
||||
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 (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
guidanceDepth += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token.type === 'container_do_close' || token.type === 'container_dont_close') {
|
||||
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 (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
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 (token.type === 'container_do_open' || token.type === 'container_dont_open') {
|
||||
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;
|
||||
};
|
||||
|
||||
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: 'do' | 'dont'): 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 =
|
||||
type === '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`
|
||||
: `<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`;
|
||||
|
||||
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'));
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
|
||||
@@ -22,10 +21,14 @@ 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: Token[], index: number) => {
|
||||
const renderFunc = (tokens: Token[], idx: number) => {
|
||||
const renderSandbox = (tokenList: any[], index: number) => {
|
||||
const renderFunc = (tokens: any[], idx: number) => {
|
||||
if (tokens[idx].nesting === 1) {
|
||||
const fileAttr: string[] = [];
|
||||
const attrs = Object.fromEntries(tokens[idx].attrs || []);
|
||||
|
||||
const files: Record<
|
||||
@@ -44,7 +47,7 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
) {
|
||||
if (tokens[i].type === 'fence' && tokens[i].tag === 'code') {
|
||||
const info = tokens[i].info ?? '';
|
||||
const [, fileName, params = ''] = info.split(' ');
|
||||
const [lang, fileName, params = ''] = info.split(' ');
|
||||
|
||||
const active = params.includes('[active]');
|
||||
const hidden = params.includes('[hidden]');
|
||||
@@ -54,9 +57,15 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +122,7 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
return renderFunc(tokenList, index);
|
||||
};
|
||||
|
||||
function createCodeGroup(): ContainerArgs {
|
||||
function createCodeGroup(md: MarkdownIt): ContainerArgs {
|
||||
return [
|
||||
container,
|
||||
'sandpack',
|
||||
@@ -125,5 +134,5 @@ export default function sandpackPlugin(md: MarkdownIt, pluginOptions: SnackParam
|
||||
];
|
||||
}
|
||||
|
||||
md.use(...createCodeGroup());
|
||||
md.use(...createCodeGroup(md));
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ 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';
|
||||
@@ -87,7 +88,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(params.theme ?? 'light')}"` +
|
||||
// ` data-snack-theme="${escapeHtml(theme)}"` +
|
||||
` data-snack-preview="${escapeHtml(preview)}"` +
|
||||
` data-snack-loading="${escapeHtml(loading)}"` +
|
||||
` data-snack-device-appearance="${escapeHtml(deviceAppearance)}"` +
|
||||
|
||||
@@ -72,11 +72,6 @@ export const reactNativeSidebar = [
|
||||
link: '/guide/react-native/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
{
|
||||
text: 'Optimizations',
|
||||
link: '/guide/react-native/advanced/optimizations',
|
||||
desc: 'Optimizing your app for performance',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,91 +11,41 @@ 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: 'Contribution guide',
|
||||
link: '/contribute/',
|
||||
text: 'Contributing',
|
||||
link: '/contribute',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Contributing icons',
|
||||
text: 'Designing icons',
|
||||
items: [
|
||||
{
|
||||
text: 'Getting started',
|
||||
link: '/contribute/icons/',
|
||||
text: 'Icon Design Principles',
|
||||
link: '/contribute/icon-design-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 Illustrator',
|
||||
link: '/contribute/illustrator-guide',
|
||||
},
|
||||
{
|
||||
text: 'Design specification',
|
||||
link: '/contribute/icons/specification',
|
||||
desc: 'Review the technical requirements every Lucide icon must meet.',
|
||||
text: 'Designing in Inkscape',
|
||||
link: '/contribute/inkscape-guide',
|
||||
},
|
||||
{
|
||||
text: 'Naming conventions',
|
||||
link: '/contribute/icons/naming-conventions',
|
||||
desc: 'Learn how to choose clear, consistent, and predictable names for Lucide icons.',
|
||||
text: 'Designing in Figma',
|
||||
link: '/contribute/figma-guide',
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
text: 'Designing in Affinity Designer',
|
||||
link: '/contribute/affinity-designer-guide',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import IconButton from './base/IconButton.vue';
|
||||
import { x } from '../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import VPButton from 'vitepress/dist/client/theme-default/components/VPButton.vue';
|
||||
|
||||
const showAskSurvey = useLocalStorage('show-ask-overlay', true, {
|
||||
initOnMounted: true,
|
||||
});
|
||||
|
||||
const delayedShow = ref(false)
|
||||
|
||||
const showPopup = computed(() => showAskSurvey.value && delayedShow.value );
|
||||
let isInitialized = false
|
||||
|
||||
function startTally() {
|
||||
window.Tally.openPopup('EkvOpB', {
|
||||
layout: 'modal',
|
||||
width: 700,
|
||||
autoClose: 5000,
|
||||
onClose() {
|
||||
showAskSurvey.value = false;
|
||||
},
|
||||
onSubmit() {
|
||||
showAskSurvey.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
delayedShow.value = true
|
||||
}, 10000)
|
||||
})
|
||||
|
||||
function initTally() {
|
||||
try {
|
||||
if (!isInitialized) {
|
||||
isInitialized = true
|
||||
const s = document.createElement('script')
|
||||
s.src = `//tally.so/widgets/embed.js`
|
||||
s.async = true
|
||||
s.onload = startTally
|
||||
document.body.appendChild(s)
|
||||
} else {
|
||||
startTally();
|
||||
}
|
||||
} catch (error) {
|
||||
window.open('https://tally.so/r/EkvOpB', '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
function hidePopup() {
|
||||
showAskSurvey.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
:class="{
|
||||
'show-popup': showPopup,
|
||||
}"
|
||||
class="ask-overlay"
|
||||
>
|
||||
<IconButton
|
||||
@click="hidePopup"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
:iconNode="x"
|
||||
:size="20"
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</IconButton>
|
||||
|
||||
<h2 class="title">
|
||||
We value your feedback!
|
||||
</h2>
|
||||
|
||||
<p class="description">
|
||||
Can you spare a moment to take our survey? <br>Your feedback helps us improve Lucide and make it better for everyone.
|
||||
</p>
|
||||
|
||||
<div class="buttons">
|
||||
<VPButton
|
||||
text="Take the Survey"
|
||||
theme="brand"
|
||||
@click="initTally"
|
||||
/>
|
||||
|
||||
<VPButton
|
||||
text="Close"
|
||||
theme="alt"
|
||||
@click="hidePopup"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ask-overlay {
|
||||
position: fixed;
|
||||
z-index: 99;;
|
||||
bottom: 24px;
|
||||
width: calc(100% - 48px);
|
||||
max-width: 420px;
|
||||
left: 24px;
|
||||
transition:
|
||||
opacity 0.25s,
|
||||
transform 0.5s cubic-bezier(0.19, 1, 0.22, 1);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--vp-shadow-4);
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
opacity: 0;
|
||||
transform: translateY(200px);
|
||||
}
|
||||
|
||||
.ask-overlay .title {
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ask-overlay .description {
|
||||
padding-top: 8px;
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.ask-overlay.show-popup {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition:
|
||||
opacity 0.5s,
|
||||
transform 0.25s ease;
|
||||
}
|
||||
|
||||
.ask-overlay .buttons {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.hide-button {
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 3;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
@@ -11,14 +11,6 @@ const { isDark } = useData();
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function limitHexInput(val: string) {
|
||||
if (val.startsWith('#')) {
|
||||
return val.slice(0, 9);
|
||||
}
|
||||
|
||||
return val.slice(0, 8);
|
||||
}
|
||||
|
||||
const value = computed({
|
||||
get: () => {
|
||||
if (props.modelValue == null || props.modelValue === 'currentColor') {
|
||||
@@ -27,7 +19,7 @@ const value = computed({
|
||||
|
||||
return props.modelValue;
|
||||
},
|
||||
set: (val) => emit('update:modelValue', limitHexInput(val)),
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -49,7 +41,6 @@ const value = computed({
|
||||
class="color-input-text"
|
||||
aria-label="Color picker input"
|
||||
v-model="value"
|
||||
maxlength="9"
|
||||
placeholder="[default]"
|
||||
/>
|
||||
</div>
|
||||
@@ -100,7 +91,6 @@ const value = computed({
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 13px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
cursor: text;
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="top-notification">
|
||||
<p>
|
||||
<Icon
|
||||
:iconNode="RocketDuotoneIcon"
|
||||
size="18"
|
||||
class="rocket-icon"
|
||||
/>
|
||||
<VPLink
|
||||
class="notification-link"
|
||||
href="/guide/version-1"
|
||||
>Lucide v1 is out!</VPLink
|
||||
>
|
||||
>🚀<br />
|
||||
You're looking at the site for v1, for v0 go to
|
||||
<VPLink
|
||||
class="notification-link"
|
||||
@@ -28,7 +21,11 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 960px) {
|
||||
:root {
|
||||
--vp-layout-top-height: 64px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
:root {
|
||||
--vp-layout-top-height: 32px;
|
||||
}
|
||||
@@ -37,19 +34,19 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
|
||||
|
||||
<style scoped>
|
||||
.top-notification {
|
||||
position: sticky;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
height: var(--vp-layout-top-height);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--vp-c-brand-dark);
|
||||
color: var(--vp-c-bg-alt-down);
|
||||
font-size: 14px;
|
||||
padding: 4px 16px;
|
||||
text-wrap: balance;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.notification-link {
|
||||
@@ -61,9 +58,9 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.rocket-icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
@media (min-width: 640px) {
|
||||
.top-notification br {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -6,10 +6,10 @@ import SelectIcon from './SelectIcon.vue';
|
||||
|
||||
defineProps<{
|
||||
id?: string;
|
||||
items?: { name: string; value: string; icon?: string; iconDark?: string }[];
|
||||
items?: { name: string; route: string; icon: string; iconDark?: string }[];
|
||||
}>();
|
||||
|
||||
const selected = defineModel<{ name: string; value: string; icon?: string; iconDark?: string }>();
|
||||
const selected = defineModel<{ name: string; icon: string; iconDark?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,18 +19,14 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
class="select-button"
|
||||
:id="id"
|
||||
>
|
||||
<slot name="start-icon">
|
||||
<SelectIcon v-if="selected.icon" :item="selected" />
|
||||
</slot>
|
||||
<SelectIcon :item="selected" />
|
||||
<span class="select-text">{{ selected.name }}</span>
|
||||
<span class="select-icon">
|
||||
<slot name="end-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</slot>
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
@@ -47,9 +43,8 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
|
||||
:value="item"
|
||||
as="template"
|
||||
>
|
||||
|
||||
<li :class="['select-option', { active, selected }]">
|
||||
<SelectIcon v-if="item.icon" :item="item" />
|
||||
<SelectIcon :item="item" />
|
||||
<span :class="['option-text', { selected }]">{{ item.name }}</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
|
||||
@@ -2,28 +2,25 @@
|
||||
const { item } = defineProps<{
|
||||
item: {
|
||||
name: string;
|
||||
icon?: string;
|
||||
icon: string;
|
||||
iconDark?: string;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="item.icon || item.iconDark">
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,49 +11,49 @@ const { page } = useData();
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', value: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', value: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', value: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', value: '/guide/preact/' },
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact/' },
|
||||
{
|
||||
name: 'React Native',
|
||||
icon: '/framework-logos/react-native.svg',
|
||||
value: '/guide/react-native/',
|
||||
route: '/guide/react-native/',
|
||||
},
|
||||
{
|
||||
name: 'Astro',
|
||||
icon: '/framework-logos/astro.svg',
|
||||
iconDark: '/framework-logos/astro-dark.svg',
|
||||
value: '/guide/astro/',
|
||||
route: '/guide/astro/',
|
||||
},
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', value: '/guide/static/' },
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', route: '/guide/static/' },
|
||||
];
|
||||
|
||||
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1]);
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ value }) => {
|
||||
const current = frameworks.find(({ route }) => {
|
||||
if (router.route.path?.startsWith?.('/guide')) {
|
||||
const [, , framework] = router.route.path.split('/');
|
||||
const [, frameWorkRoute] = value.split('/').filter(Boolean);
|
||||
const [, frameWorkRoute] = route.split('/').filter(Boolean);
|
||||
|
||||
return framework === frameWorkRoute;
|
||||
}
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === value;
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === route;
|
||||
});
|
||||
|
||||
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
|
||||
return current || fallbackFramework.value;
|
||||
});
|
||||
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; route: string }) {
|
||||
fallbackFramework.value = item;
|
||||
if (item.value !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.value, item.value);
|
||||
if (item.route !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.route, item.route);
|
||||
|
||||
const hasRoute = sidebar[item.value]?.some((section) =>
|
||||
const hasRoute = sidebar[item.route]?.some((section) =>
|
||||
section?.items?.some(({ link }) => link === likeRoute),
|
||||
);
|
||||
|
||||
@@ -62,7 +62,7 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
return;
|
||||
}
|
||||
|
||||
router.go(item.value);
|
||||
router.go(item.route);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -85,12 +85,12 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
/>
|
||||
</div>
|
||||
<VPSidebarGroup
|
||||
:key="selected.value"
|
||||
:key="selected.route"
|
||||
v-if="
|
||||
page?.relativePath?.startsWith?.('guide') &&
|
||||
!page?.relativePath?.startsWith?.(selected.value.substring(1))
|
||||
!page?.relativePath?.startsWith?.(selected.route.substring(1))
|
||||
"
|
||||
:items="sidebar[selected.value]"
|
||||
:items="sidebar[selected.route]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -118,15 +118,5 @@ label {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.framework-select:before {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
inset: 0 -16px -24px;
|
||||
background: color-mix(in srgb, var(--vp-c-bg-alt), transparent 50%);
|
||||
backdrop-filter: blur(4px);
|
||||
mask-image: linear-gradient(to bottom, black calc(100% - 16px), transparent 100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,7 +53,7 @@ const pickIcons = [
|
||||
'terminal',
|
||||
'thermometer',
|
||||
'thumbs-up',
|
||||
'trash',
|
||||
'trash-2',
|
||||
'umbrella',
|
||||
'watch',
|
||||
'sparkles',
|
||||
|
||||
@@ -3,7 +3,25 @@ import Badge from '../base/Badge.vue';
|
||||
import { moveRight } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { data } from './HomeHeroInfoBefore.data';
|
||||
import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
|
||||
const rocketDuoTone = [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M8 18a2 2 0 0 0-2-2c-2.53-.078-3.247 3.605-3.5 5.5 1.895-.253 5.578-.97 5.5-3.5',
|
||||
fill: 'currentColor',
|
||||
opacity: 0.5,
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M22 2c0 2.72-.78 7.5-6 11 0 0 1.08 3.38 0 5-.969 1.448-4 2-4 2v-5l-3-3H4s.553-3.033 2-4c1.616-1.077 4.982.043 5 .05A12.88 12.88 0 0 1 22 2m-6 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6',
|
||||
fill: 'currentColor',
|
||||
'fill-rule': 'evenodd',
|
||||
'clip-rule': 'evenodd',
|
||||
},
|
||||
],
|
||||
] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,7 +33,7 @@ import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
|
||||
class="badge-special confetti-button animate"
|
||||
href="/guide/version-1"
|
||||
>
|
||||
<Icon :iconNode="RocketDuotoneIcon" />
|
||||
<Icon :iconNode="rocketDuoTone" />
|
||||
Version 1
|
||||
<Icon
|
||||
:iconNode="moveRight"
|
||||
|
||||
@@ -13,21 +13,23 @@ const { go } = useRouter()
|
||||
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||
<div class="packages-list">
|
||||
<a
|
||||
v-for="{ name, label, path, logo, logoDark } in data.packages"
|
||||
:href="path"
|
||||
v-for="{ name, logo, logoDark } in data.packages"
|
||||
:href="`/guide/packages/${name}`"
|
||||
class="package-logo"
|
||||
@click.prevent="go(path)"
|
||||
:aria-label="`Read more about: ${name} package`"
|
||||
@click.prevent="go(`/guide/packages/${name}`)"
|
||||
>
|
||||
<img
|
||||
:src="logo"
|
||||
:class="{ light: logoDark, 'image-logo': true }"
|
||||
:alt="label"
|
||||
:alt="`${name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="logoDark"
|
||||
:src="logoDark"
|
||||
:alt="label"
|
||||
:alt="`${name} logo`"
|
||||
class="image-logo dark"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const RocketDuotoneIcon = [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M8 18a2 2 0 0 0-2-2c-2.53-.078-3.247 3.605-3.5 5.5 1.895-.253 5.578-.97 5.5-3.5',
|
||||
fill: 'currentColor',
|
||||
opacity: 0.5,
|
||||
},
|
||||
],
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M22 2c0 2.72-.78 7.5-6 11 0 0 1.08 3.38 0 5-.969 1.448-4 2-4 2v-5l-3-3H4s.553-3.033 2-4c1.616-1.077 4.982.043 5 .05A12.88 12.88 0 0 1 22 2m-6 3a3 3 0 1 0 0 6 3 3 0 0 0 0-6',
|
||||
fill: 'currentColor',
|
||||
'fill-rule': 'evenodd',
|
||||
'clip-rule': 'evenodd',
|
||||
},
|
||||
],
|
||||
] as const;
|
||||
|
||||
export default RocketDuotoneIcon;
|
||||
@@ -5,18 +5,11 @@ import IconButton from '../base/IconButton.vue';
|
||||
import VPDocAsideCarbonAds from 'vitepress/dist/client/theme-default/components/VPDocAsideCarbonAds.vue';
|
||||
import { x } from '../../../data/iconNodes';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
const { theme } = useData();
|
||||
const showAd = useSessionStorage('show-carbon-ads', true, {
|
||||
initOnMounted: true,
|
||||
});
|
||||
const showAd = useSessionStorage('show-carbon-ads', true);
|
||||
const carbonLoaded = ref(true);
|
||||
const shouldHideAd = computed(() => !showAd.value || !carbonLoaded.value);
|
||||
|
||||
function hideAd() {
|
||||
showAd.value = false;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
drawerOpen: boolean;
|
||||
@@ -35,13 +28,13 @@ onMounted(() => {
|
||||
<div
|
||||
:class="{
|
||||
'drawer-open': drawerOpen,
|
||||
'hide-ad': shouldHideAd,
|
||||
'hide-ad': !(showAd && carbonLoaded),
|
||||
}"
|
||||
class="floating-ad"
|
||||
v-if="theme.carbonAds"
|
||||
>
|
||||
<IconButton
|
||||
@click="hideAd"
|
||||
@click="showAd = false"
|
||||
class="hide-button"
|
||||
>
|
||||
<Icon
|
||||
@@ -50,7 +43,7 @@ onMounted(() => {
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
</IconButton>
|
||||
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
|
||||
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -73,7 +66,6 @@ onMounted(() => {
|
||||
.floating-ad.hide-ad {
|
||||
transform: translateX(224px);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.floating-ad.drawer-open.hide-ad {
|
||||
@@ -104,7 +96,6 @@ onMounted(() => {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 3;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getAllData } from '../../../lib/icons';
|
||||
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
|
||||
import iconsMetaData from '../../../data/iconMetaData';
|
||||
|
||||
|
||||
@@ -7,14 +7,10 @@ import { useActiveAnchor } from '../../composables/useActiveAnchor'
|
||||
import { data } from './CategoryList.data'
|
||||
import CategoryListItem from './CategoryListItem.vue'
|
||||
import SidebarTitle from './SidebarTitle.vue'
|
||||
import { useCategoryViewSync } from '../../composables/useCategoryView'
|
||||
import { useCategoryView } from '../../composables/useCategoryView'
|
||||
|
||||
const { page } = useData()
|
||||
const { categoryCounts, selectedCategory } = useCategoryViewSync();
|
||||
|
||||
function onCategoriesClick() {
|
||||
selectedCategory.value = '';
|
||||
}
|
||||
const { categoryCounts } = useCategoryView();
|
||||
|
||||
const categoriesIsActive = computed(() => {
|
||||
return isActive(page.value.relativePath, '/icons/categories');
|
||||
@@ -57,7 +53,7 @@ watch(headers, () => {
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/" :class="{ 'active': overviewIsActive } ">
|
||||
All
|
||||
</VPLink>
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive }" @click="onCategoriesClick">
|
||||
<VPLink class="sidebar-link sidebar-text" href="/icons/categories" :class="{ 'active': categoriesIsActive } ">
|
||||
Categories
|
||||
</VPLink>
|
||||
<div class="content">
|
||||
|
||||
@@ -28,10 +28,7 @@ function onClick(categoryName: string) {
|
||||
const heading = document.querySelector<HTMLAnchorElement>(categoryName);
|
||||
heading?.focus();
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.pathname = '/icons/categories';
|
||||
url.hash = categoryName;
|
||||
window.history.pushState({}, '', url);
|
||||
window.history.pushState({}, '', `/icons/categories#${categoryName}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -88,7 +85,6 @@ function onClick(categoryName: string) {
|
||||
|
||||
.outline-link.inactive {
|
||||
color: var(--vp-c-text-4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.outline-link.nested {
|
||||
|
||||
@@ -188,7 +188,7 @@ const deprecatedTitle = computed(() => {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
scrollbar-color: currentColor transparent; /* foreground background */
|
||||
}
|
||||
.horizontal-scroller::-webkit-scrollbar {
|
||||
width: 0;
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { IconEntity } from '../../types';
|
||||
import { computed, ref } from 'vue';
|
||||
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
|
||||
import { useIconStyleContext } from '../../composables/useIconStyle';
|
||||
import { squarePen } from '../../../data/iconNodes';
|
||||
|
||||
const props = defineProps<{
|
||||
name: IconEntity['name'];
|
||||
@@ -20,31 +19,6 @@ const iconComponent = computed(() => {
|
||||
if (!props.name || !props.iconNode) return null;
|
||||
return createLucideIcon(props.name, props.iconNode);
|
||||
});
|
||||
|
||||
const SquarePenIcon = createLucideIcon('SquarePen', squarePen);
|
||||
|
||||
const innerSvg = computed(() => {
|
||||
if (!props.iconNode) return '';
|
||||
|
||||
const string = props.iconNode
|
||||
.map(([tag, attrs]) => {
|
||||
const attributes = Object.entries(attrs)
|
||||
.map(([key, value]) => `${key}="${value}"`)
|
||||
.join(' ');
|
||||
return `<${tag} ${attributes}/>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return string
|
||||
.replace(/>[\r\n ]+</g, '><')
|
||||
.replace(/(<.*?>)|\s+/g, (m, $1) => $1 || ' ')
|
||||
.trim();
|
||||
});
|
||||
|
||||
const base64InnerSvg = computed(() => {
|
||||
if (typeof window === 'undefined') return '';
|
||||
return btoa(innerSvg.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -83,16 +57,6 @@ const base64InnerSvg = computed(() => {
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<a
|
||||
: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"
|
||||
title="Open in Lucide Studio"
|
||||
aria-label="Open in Lucide Studio"
|
||||
>
|
||||
<SquarePenIcon :size="14" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -111,8 +75,6 @@ const base64InnerSvg = computed(() => {
|
||||
position: relative;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-container > :deep(svg:not(.icon-grid)) {
|
||||
width: 100%;
|
||||
@@ -123,34 +85,6 @@ const base64InnerSvg = computed(() => {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.studio-button {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--vp-c-text-2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.icon-container:hover .studio-button,
|
||||
.studio-button:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.studio-button:hover,
|
||||
.studio-button:focus-visible {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.icon-component.customizable {
|
||||
will-change: width, height, stroke-width, stroke;
|
||||
/* color: var(--customize-color, currentColor);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, defineAsyncComponent, onMounted } from 'vue';
|
||||
import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import type { IconEntity, Category } from '../../types';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
@@ -12,7 +12,6 @@ import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import useScrollToCategory from '../../composables/useScrollToCategory';
|
||||
import { useCategoryView } from '../../composables/useCategoryView';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
|
||||
@@ -27,15 +26,8 @@ const props = defineProps<{
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const { searchInput, searchQuery, searchQueryDebounced } = useSearchInput();
|
||||
const { selectedCategory } = useCategoryView();
|
||||
const isSearching = computed(() => !!searchQuery.value);
|
||||
|
||||
watch(searchQueryDebounced, (searchString) => {
|
||||
if (searchString !== '') {
|
||||
selectedCategory.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
searchInput.value?.focus();
|
||||
});
|
||||
@@ -44,12 +36,8 @@ function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
|
||||
const {
|
||||
execute: fetchCategories,
|
||||
data: categoriesMap,
|
||||
isFetching: isFetchingCategories,
|
||||
} = useFetchCategories();
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
@@ -121,9 +109,6 @@ const categoriesList = computed(() => {
|
||||
}, []);
|
||||
});
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
|
||||
);
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
@@ -142,39 +127,22 @@ onMounted(() => {
|
||||
useEventListener(window, 'scroll', containerProps.onScroll);
|
||||
});
|
||||
|
||||
function loadSearchMetadata() {
|
||||
if (tags.value == null && !isFetchingTags.value) {
|
||||
void fetchTags();
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
}
|
||||
if (categoriesMap.value == null && !isFetchingCategories.value) {
|
||||
void fetchCategories();
|
||||
if (categoriesMap.value == null) {
|
||||
fetchCategories();
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchQuery, (searchString) => {
|
||||
if (searchString !== '') {
|
||||
loadSearchMetadata();
|
||||
}
|
||||
});
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
const url = new URL(window.location);
|
||||
url.pathname = '/icons/categories';
|
||||
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
if (selectedCategory.value) {
|
||||
url.hash = selectedCategory.value;
|
||||
}
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
window.history.pushState({}, '', '/icons/categories');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -182,7 +150,6 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
@@ -191,11 +158,11 @@ function handleCloseDrawer() {
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="loadSearchMetadata"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { IconEntity } from '../../types';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import { useRoute } from 'vitepress';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import Select from '../base/Select.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
@@ -15,26 +14,9 @@ import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { listSortDescending } from '~/.vitepress/data/iconNodes';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
const SORTING = [
|
||||
{
|
||||
name: 'Popularity',
|
||||
value: 'popularity',
|
||||
},
|
||||
{
|
||||
name: 'Release date',
|
||||
value: 'release-date',
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const initialGridItems = computed(() => {
|
||||
if (containerWidth.value === 0) return 120;
|
||||
@@ -50,14 +32,9 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const selectedSort = ref(SORTING[0])
|
||||
|
||||
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
|
||||
const {
|
||||
execute: fetchCategories,
|
||||
data: categories,
|
||||
isFetching: isFetchingCategories,
|
||||
} = useFetchCategories();
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
@@ -66,35 +43,14 @@ const columnSize = computed(() => {
|
||||
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
|
||||
});
|
||||
|
||||
const sortedIcons = computed(() => {
|
||||
switch (selectedSort.value.value) {
|
||||
case 'popularity':
|
||||
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
|
||||
case 'release-date':
|
||||
return [...props.icons].sort((a, b) => {
|
||||
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
|
||||
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
|
||||
return bDate - aDate;
|
||||
});
|
||||
case 'name':
|
||||
return [...props.icons].sort((a, b) => a.name.localeCompare(b.name));
|
||||
default:
|
||||
return props.icons;
|
||||
}
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return sortedIcons.value;
|
||||
return props.icons;
|
||||
}
|
||||
|
||||
if (categories.value == null) {
|
||||
return sortedIcons.value;
|
||||
}
|
||||
|
||||
return sortedIcons.value.map((icon) => {
|
||||
return props.icons.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value[icon.name] ?? [];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
|
||||
return {
|
||||
...icon,
|
||||
@@ -112,15 +68,11 @@ const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 8 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
|
||||
);
|
||||
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
@@ -146,21 +98,15 @@ function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
function loadSearchMetadata() {
|
||||
if (tags.value == null && !isFetchingTags.value) {
|
||||
void fetchTags();
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
}
|
||||
if (categories.value == null && !isFetchingCategories.value) {
|
||||
void fetchCategories();
|
||||
if (categories.value == null) {
|
||||
fetchCategories();
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchQuery, (searchString) => {
|
||||
if (searchString !== '') {
|
||||
loadSearchMetadata();
|
||||
}
|
||||
});
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
@@ -172,14 +118,7 @@ watch(searchQueryDebounced, () => {
|
||||
function handleCloseDrawer() {
|
||||
setActiveIconName('');
|
||||
|
||||
const url = new URL(window.location);
|
||||
url.pathname = '/icons/';
|
||||
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
window.history.pushState({}, '', '/icons/');
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -187,7 +126,6 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
@@ -196,25 +134,11 @@ function handleCloseDrawer() {
|
||||
ref="searchInput"
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="input-wrapper"
|
||||
@focus="loadSearchMetadata"
|
||||
@focus="onFocusSearchInput"
|
||||
/>
|
||||
|
||||
<Select
|
||||
id="sort-select"
|
||||
:items="SORTING"
|
||||
v-model="selectedSort"
|
||||
>
|
||||
<template #start-icon>
|
||||
<Icon
|
||||
:iconNode="listSortDescending"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</Select>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
|
||||
@@ -73,11 +73,13 @@ const customizingActive = computed(() => {
|
||||
id="icon-color"
|
||||
label="Color"
|
||||
>
|
||||
<ColorPicker
|
||||
v-model="color"
|
||||
id="icon-color"
|
||||
class="color-picker"
|
||||
/>
|
||||
<template #display>
|
||||
<ColorPicker
|
||||
v-model="color"
|
||||
id="icon-color"
|
||||
class="color-picker"
|
||||
/>
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
@@ -159,6 +161,6 @@ const customizingActive = computed(() => {
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,63 +9,20 @@
|
||||
margin-bottom: 16px;
|
||||
position: sticky;
|
||||
z-index: 10;
|
||||
top: var(--vp-layout-top-height);
|
||||
top: 48px;
|
||||
padding-top: 24px;
|
||||
margin-top: -32px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin-bottom: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 14px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button):hover,
|
||||
.search-bar :deep(.select-button):focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-options) {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-option) {
|
||||
padding-right: 36px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 16px 11px 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.search-bar {
|
||||
padding-top: 32px;
|
||||
margin-top: calc((var(--vp-layout-top-height, 0px) + 32px) * -1);
|
||||
top: calc(var(--vp-layout-top-height, 0px) + 64px);
|
||||
top: 64px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -32,8 +32,7 @@
|
||||
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%),
|
||||
@@ -57,8 +56,7 @@
|
||||
.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%),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user