Compare commits

..
Author SHA1 Message Date
Eric Fennis 1e37a6c4ec Add automatic comment for generating missing issue 2026-09-04 10:45:58 +02:00
807 changed files with 13649 additions and 30320 deletions
-1
View File
@@ -10,4 +10,3 @@ touchpad
ungroup
toc
sunlounger
monstera
+1 -11
View File
@@ -13,7 +13,7 @@ The `contributors` property is a required array of GitHub usernames for the peop
The `tags` property is an array of strings that describe the icon and can be used for searching.
Validate the tags against the `icon.schema.json` to ensure they are correctly formatted and adhere to the defined structure.
Provide tag suggestions based on the name of the icon and the use cases provided in the PR description. Use the existing tags in the repository as a reference for consistency and to avoid duplicates. Don't suggest words like: 'icon' and preferably use single words. Tags should always be in lowercase, and may also contain spaces (e.g. `magnifying glass`). Don't include the full icon name, its space-separated form, or any individual part of its kebab-case name because those terms are already searchable. For example, don't suggest `mail-search`, `mail search`, `mail`, or `search` for the `mail-search` icon.
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
@@ -57,16 +57,6 @@ When a request covers several icons at once, assign each use-case only to the ic
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:
@@ -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.
-6
View File
@@ -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:
@@ -82,7 +77,6 @@
- changed-files:
- any-glob-to-any-file:
- 'packages/lucide-solid/*'
- 'packages/solid/*'
# For changes in the Lucide astro package
🚀 astro package:
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -31,7 +31,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
+41 -86
View File
@@ -5,10 +5,7 @@ on:
branches:
- main
paths:
- icons/*.svg
schedule:
- cron: '0 6 * * *'
- icons/**/*.svg
workflow_dispatch:
inputs:
@@ -29,72 +26,47 @@ on:
permissions: {}
jobs:
release-gate:
concurrency:
group: ci-${{ github.event_name == 'schedule' && github.ref || 'release-gated' }}
cancel-in-progress: true
if: github.repository == 'lucide-icons/lucide' && github.event_name != 'workflow_dispatch'
environment: ${{ github.event_name == 'schedule' && 'Icon Release' || 'Icon Release Gated' }}
runs-on: ubuntu-latest
steps:
- name: Approve release
run: echo 'Release approved'
prepare-release:
needs: release-gate
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 # Required to check if a new release is needed
contents: read
outputs:
SHOULD_RELEASE: ${{ steps.changes.outputs.SHOULD_RELEASE }}
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
- name: Get latest tag
id: latest-tag
run: echo "LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" >> "$GITHUB_OUTPUT"
- name: Fetch tags
run: git fetch --all --tags
- name: Log latest tag
run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}'
- name: Check for releasable changes
id: changes
env:
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
- name: Check if latest tag was created today
id: environment
run: |
if [ -z "$LATEST_TAG" ]; then
echo "::error::No version tag found to release from."
exit 1
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
echo "Latest version tag: $LATEST_TAG"
CHANGES=$(git diff --name-only "$LATEST_TAG" HEAD -- ':(glob)icons/*.svg')
if [ -z "$CHANGES" ]; then
echo "::notice::No icon SVGs changed since $LATEST_TAG. Skipping release."
echo "SHOULD_RELEASE=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "$CHANGES"
echo "SHOULD_RELEASE=true" >> "$GITHUB_OUTPUT"
create-release:
if: github.repository == 'lucide-icons/lucide' && needs.prepare-release.outputs.SHOULD_RELEASE == 'true'
needs: prepare-release
if: github.repository == 'lucide-icons/lucide'
needs: check-dispatch-gate
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
runs-on: ubuntu-latest
permissions:
contents: write # Required to create the release and tag
outputs:
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
RELEASE_URL: ${{ steps.create-release.outputs.url }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -103,26 +75,36 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Fetch tags
run: git fetch --all --tags
- name: Get latest tag
id: latest-tag
run: echo "LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)" >> $GITHUB_OUTPUT
- name: Log latest tag
run: echo '${{ steps.latest-tag.outputs.LATEST_TAG }}'
- name: Check if we can patch
run: pnpm semver "$LATEST_TAG" -i minor
run: pnpm semver $LATEST_TAG -i minor
env:
LATEST_TAG: ${{ needs.prepare-release.outputs.LATEST_TAG }}
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
- name: Create new version
id: new-version
run: echo "NEW_VERSION=$(pnpm semver "$LATEST_TAG" -i minor)" >> "$GITHUB_OUTPUT"
run: echo "NEW_VERSION=$(pnpm semver $LATEST_TAG -i minor)" >> $GITHUB_OUTPUT
env:
LATEST_TAG: ${{ needs.prepare-release.outputs.LATEST_TAG }}
LATEST_TAG: ${{ steps.latest-tag.outputs.LATEST_TAG }}
- name: Check output
run: echo '${{ steps.new-version.outputs.NEW_VERSION }}'
run: |
echo '${{ steps.new-version.outputs.NEW_VERSION }}'
echo '${{ steps.change-log.outputs.CHANGE_LOG }}'
- name: Create Release
id: create-release
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
with:
tag_name: ${{ steps.new-version.outputs.NEW_VERSION }}
target_commitish: ${{ github.sha }}
name: Version ${{ steps.new-version.outputs.NEW_VERSION }}
generate_release_notes: true
@@ -147,30 +129,3 @@ jobs:
with:
version: ${{ inputs.version }}
tag: ${{ inputs.tag }}
post-release:
name: Trigger post-release automation
if: >-
!cancelled() &&
github.repository == 'lucide-icons/lucide' &&
(needs.start-release.result == 'success' || needs.dispatch-release.result == 'success')
needs: [create-release, start-release, dispatch-release]
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Dispatch post_release event
env:
GH_TOKEN: ${{ secrets.POST_RELEASE_TOKEN }}
RELEASE_TAG: ${{ needs.create-release.outputs.VERSION || inputs.version }}
RELEASE_URL: ${{ needs.create-release.outputs.RELEASE_URL }}
run: |
if [ -z "$RELEASE_URL" ]; then
RELEASE_URL="https://github.com/lucide-icons/lucide/releases/tag/$RELEASE_TAG"
fi
gh api \
--method POST \
repos/lucide-icons/post-release/dispatches \
--field event_type=post_release \
--field "client_payload[release_tag]=$RELEASE_TAG" \
--field "client_payload[release_url]=$RELEASE_URL"
-55
View File
@@ -1,55 +0,0 @@
name: Docs Checks
on:
pull_request:
paths:
- docs/**
- .github/workflows/docs.yml
- 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
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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 docs
run: pnpm --filter @lucide/docs build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Release metadata is generated by diffing every release tag
fetch-depth: 0
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Generate docs data
run: pnpm --filter @lucide/docs run "/^prebuild:.*/"
- name: Typecheck
run: pnpm --filter @lucide/docs typecheck
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -33,7 +33,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -49,7 +49,7 @@ jobs:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
+2 -18
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
@@ -46,19 +46,3 @@ jobs:
- name: Test
run: pnpm --filter @lucide/astro test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'package.json'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm --filter @lucide/astro typecheck
+1 -17
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -37,19 +37,3 @@ jobs:
with:
name: lucide-font
path: lucide-font
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter build-font typecheck
+1 -18
View File
@@ -3,7 +3,6 @@ name: Lucide Lab checks
on:
pull_request:
paths:
- lab/**
- packages/lab/**
- packages/shared/**
- tools/build-icons/**
@@ -18,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -29,19 +28,3 @@ jobs:
- name: Build
run: pnpm --filter @lucide/lab build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter @lucide/lab typecheck
+1 -17
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -31,19 +31,3 @@ jobs:
- name: Test
run: pnpm --filter lucide-preact test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide-preact typecheck
+2 -18
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -47,19 +47,3 @@ jobs:
- name: Test
run: pnpm --filter lucide-react-native test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide-react-native typecheck
+2 -40
View File
@@ -7,13 +7,10 @@ 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:
@@ -24,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -40,7 +37,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -51,38 +48,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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide-react typecheck
+2 -18
View File
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -27,20 +27,4 @@ jobs:
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/shared test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter @lucide/shared typecheck
run: pnpm --filter lucide-react test
+2 -40
View File
@@ -7,12 +7,9 @@ on:
pull_request:
paths:
- packages/lucide-solid/**
- integrations/lucide-solid/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- package.json
- pnpm-workspace.yaml
- pnpm-lock.yaml
permissions:
@@ -23,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -39,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -50,38 +47,3 @@ jobs:
- name: Test
run: pnpm --filter lucide-solid test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide-solid typecheck
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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-solid-vite exec playwright install --with-deps chromium
- name: Run integration checks
run: pnpm test:integrations:solid
+1 -17
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -26,19 +26,3 @@ jobs:
- name: Build
run: pnpm --filter lucide-static build
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide-static typecheck
+4 -4
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -36,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -52,7 +52,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -61,5 +61,5 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
- name: Test
run: pnpm --filter @lucide/svelte typecheck
+2 -40
View File
@@ -10,9 +10,6 @@ on:
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- integrations/lucide-vue/**
- package.json
- pnpm-workspace.yaml
- pnpm-lock.yaml
permissions:
@@ -23,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -39,7 +36,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -50,38 +47,3 @@ jobs:
- name: Test
run: pnpm --filter @lucide/vue test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Test
run: pnpm --filter @lucide/vue typecheck
integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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-vue-vite exec playwright install --with-deps chromium
- name: Run integration checks
run: pnpm test:integrations:vue
+2 -18
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -35,7 +35,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -46,19 +46,3 @@ jobs:
- name: Test
run: pnpm --filter lucide test
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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: Typecheck
run: pnpm --filter lucide typecheck
@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: lucide-icons/lucide
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -0,0 +1,82 @@
name: Pull request missing issue
on:
pull_request_target:
types:
- opened
- edited
- synchronize
- ready_for_review
paths:
- 'icons/*.svg'
permissions: {}
# Two events landing close together (a push right after opening) would
# otherwise both find no existing comment and post one each.
concurrency:
group: missing-issue-${{ github.event.number }}
cancel-in-progress: true
jobs:
pull-request-missing-issue:
name: Pull Request Missing Issue
if: github.repository == 'lucide-icons/lucide' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # Required to post and update the comment on the pull request
steps:
# We checkout the main branch of main repository for security reasons.
# The script never runs code from the pull request, it only reads the
# pull request's icons through the GitHub API.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: lucide-icons/lucide
- 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: Generate comment markup
id: markup
run: node ./scripts/generateMissingIssueComment.mts
env:
PULL_REQUEST_NUMBER: ${{ github.event.number }}
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Find comment
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
id: pr-comment
with:
issue-number: ${{ github.event.number }}
comment-author: 'github-actions[bot]'
body-includes: lucide-missing-issue-comment
- name: Post missing issue comment
if: steps.markup.outputs.action == 'nag'
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.number }}
body-path: missing-issue-comment.md
edit-mode: replace
# Linking an issue from the pull request's Development sidebar fires no
# event, so the comment resolves on the next edit or push instead.
- name: Resolve missing issue comment
if: steps.markup.outputs.action == 'resolve' && steps.pr-comment.outputs.comment-id != ''
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
with:
comment-id: ${{ steps.pr-comment.outputs.comment-id }}
issue-number: ${{ github.event.number }}
body: |
<!-- lucide-missing-issue-comment -->
Thanks for linking an issue for this icon design! ✅
edit-mode: replace
-146
View File
@@ -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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- 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
+3 -4
View File
@@ -53,13 +53,12 @@ jobs:
'@lucide/angular',
'@lucide/astro',
'@lucide/icons',
'@lucide/solid',
'@lucide/svelte',
'@lucide/vue',
]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -97,7 +96,7 @@ jobs:
name: lucide-font
path: lucide-font
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
@@ -128,7 +127,7 @@ jobs:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: 'pnpm'
-71
View File
@@ -1,71 +0,0 @@
name: Lucide Solid 2 Checks
on:
push:
branches:
- next
pull_request:
paths:
- packages/solid/**
- packages/shared/**
- tools/build-icons/**
- tools/rollup-plugins/**
- integrations/lucide-solid-2/**
- 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
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter @lucide/solid build
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
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Test
run: pnpm --filter @lucide/solid 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-solid-2-vite exec playwright install --with-deps chromium
- name: Run integration checks
run: pnpm test:integrations:solid-2
+1 -13
View File
@@ -1,13 +1,11 @@
.DS_Store
.idea
.next
.astro
.obsidian
.now
.cache
.idea
.env
node_modules
.pnpm-store
dist
build
sandbox
@@ -15,13 +13,6 @@ stash
coverage
stats
*.log
*.tsbuildinfo
integrations/**/.react-router
integrations/**/.vitest-attachments
integrations/**/__screenshots__
integrations/**/.output
integrations/**/.nitro
integrations/**/.nuxt
outlined
lucide-font
packages/**/src/icons/*.js
@@ -32,9 +23,6 @@ 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
+3
View File
@@ -1,2 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
pnpm checkIcons
-11
View File
@@ -19,17 +19,6 @@ 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/**/file-routes.d.ts
integrations/**/.vitest-attachments
integrations/**/__screenshots__
integrations/**/.output
integrations/**/.nitro
integrations/**/.nuxt
# lucide-svelte
packages/svelte/src/icons/*.svelte
packages/svelte/.svelte-kit
+2 -1
View File
@@ -1,12 +1,13 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes/index.ts';
import { IconNodeWithKeys } from '../../theme/types';
import iconMetaData from '../../data/iconMetaData';
import releaseMeta from '../../data/releaseMetaData.json';
import categories from '../../data/categoriesData.json';
const dataResponse = {
icons: Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
@@ -41,6 +41,7 @@ export default eventHandler((event) => {
<Diff
oldSrc={oldSrc}
newSrc={newSrc}
showGrid
height={height}
width={width}
>
@@ -48,6 +48,7 @@ export default eventHandler((event) => {
<Diff
oldSrc={oldSrc}
newSrc={newSrc}
showGrid
height={height}
width={width}
>
+2 -1
View File
@@ -1,5 +1,6 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../data/iconNodes';
import { IconNodeWithKeys } from '../../theme/types';
export default eventHandler((event) => {
const query = getQuery(event);
@@ -18,7 +19,7 @@ export default eventHandler((event) => {
return [name, iconNode];
}
const newIconNode = iconNode.map(([name, { key, ...attrs }]) => {
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
+5 -6
View File
@@ -18,12 +18,11 @@ export default eventHandler((event) => {
return sendError(event, error);
}
const query = getQuery<Record<string, string | undefined>>(event);
const width = query.width || undefined;
const height = query.height || undefined;
const color = query.color || undefined;
const strokeWidth = query.strokeWidth || undefined;
const background = query.background || undefined;
const width = getQuery(event).width || undefined;
const height = getQuery(event).height || undefined;
const color = getQuery(event).color || undefined;
const strokeWidth = getQuery(event).strokeWidth || undefined;
const background = getQuery(event).background || undefined;
const LucideIcon = createLucideIcon(params.iconName, iconNode);
+3 -13
View File
@@ -4,7 +4,6 @@ import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-i
import sidebar from './sidebar';
import snackPlayer from './markdown/snackPlayer';
import sandpackPlugin from './markdown/sandpack';
import examplePlugin from './markdown/example';
import { readFile } from 'node:fs/promises';
import { resourcesSidebar } from './sidebar/resources';
import llmstxt from 'vitepress-plugin-llms';
@@ -15,10 +14,6 @@ const defaultSandpackCSS = await readFile(
fileURLToPath(new URL('./theme/sandpack-default.css', import.meta.url)),
'utf-8',
);
const iconStyleHeadScript = await readFile(
fileURLToPath(new URL('./theme/iconStyleHead.js', import.meta.url)),
'utf-8',
);
const title = 'Lucide';
const socialTitle = 'Lucide Icons';
@@ -36,7 +31,6 @@ export default defineConfig({
config(md) {
md.use(groupIconMdPlugin);
md.use(snackPlayer);
md.use(examplePlugin);
md.use(sandpackPlugin, {
defaultFiles: {
'/styles.css': {
@@ -88,10 +82,7 @@ export default defineConfig({
}) as unknown as UserConfig['vite']['plugins'][0],
],
},
head: [
...getHeadConfig({ title, description, socialTitle }),
['script', {}, iconStyleHeadScript],
],
head: getHeadConfig({ title, description, socialTitle }),
transformPageData,
themeConfig: {
logo: {
@@ -104,9 +95,8 @@ export default defineConfig({
{
text: 'Resources',
items: [
...resourcesSidebar[0].items.map(({ text, link }) => ({ text, link })),
{ text: 'How to use icons', link: '/how-to/' },
{ text: 'Contributing icons', link: '/contribute/icons/' },
...resourcesSidebar[0].items,
{ text: 'Design icons', link: '/contribute/icon-design-guide' },
],
},
{ text: 'Packages', link: '/packages' },
+2 -20
View File
@@ -85,26 +85,8 @@
}
]
},
"@lucide/solid": {
"order": 5,
"icon": "solid",
"docsAlias": "solid",
"packageDirname": "solid",
"shields": [
{
"alt": "npm",
"src": "https://img.shields.io/npm/v/@lucide/solid",
"href": "https://www.npmjs.com/package/@lucide/solid"
},
{
"alt": "npm",
"src": "https://img.shields.io/npm/dw/@lucide/solid",
"href": "https://www.npmjs.com/package/@lucide/solid"
}
]
},
"lucide-react-native": {
"order": 6,
"order": 5,
"icon": "react-native",
"docsAlias": "react-native",
"shields": [
@@ -121,7 +103,7 @@
]
},
"@lucide/angular": {
"order": 7,
"order": 6,
"icon": "angular",
"docsAlias": "angular",
"packageDirname": "angular",
+1 -1
View File
@@ -1,4 +1,4 @@
import React, { type JSX } from 'react';
import React from 'react';
interface BackdropProps {
src: string;
+5 -8
View File
@@ -397,14 +397,10 @@ interface SvgPreviewProps extends React.SVGProps<SVGSVGElement> {
children?: React.ReactNode;
}
const SvgPreview = ({
src,
children,
height = 24,
width = 24,
showGrid = false,
...props
}: SvgPreviewProps) => {
const SvgPreview = (
{ src, children, height = 24, width = 24, showGrid = false, ...props }: SvgPreviewProps,
ref,
) => {
const subGridSize =
Math.max(height, width) % 3 === 0
? Math.max(height, width) > 24
@@ -417,6 +413,7 @@ const SvgPreview = ({
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
width={width}
height={height}
+3 -2
View File
@@ -1,3 +1,4 @@
import { SVGProps } from 'react';
import { getCommands } from './utils';
export type Point = { x: number; y: number };
@@ -14,8 +15,8 @@ export type Path = {
};
export type PathProps<
RequiredProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof React.SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
RequiredProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
NeverProps extends keyof SVGProps<SVGPathElement | SVGRectElement | SVGCircleElement>,
> = Required<Pick<React.SVGProps<SVGElement & SVGRectElement & SVGCircleElement>, RequiredProps>> &
Omit<
React.SVGProps<SVGPathElement & SVGRectElement & SVGCircleElement>,
+4 -1
View File
@@ -53,7 +53,10 @@ export const getCommands = (src: string) =>
getNodes(src)
.map(convertToPathNode)
.flatMap(({ d, name }, idx) =>
new SVGPathData(d).toAbs().commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
new SVGPathData(d)
.toAbs()
// @ts-expect-error `commands` is typed as a union of command types svgson does not narrow
.commands.map((c, cIdx) => ({ ...c, id: idx, idx: cIdx, name })),
);
const getPaths = (src: string) => {
+3 -3
View File
@@ -4,7 +4,7 @@ import { Category, IconEntity } from '../theme/types';
const directory = path.join(process.cwd(), '../categories');
export function getAllCategoryFiles(): Omit<Category, 'iconCount'>[] {
export function getAllCategoryFiles(): Category[] {
const fileNames = fs.readdirSync(directory).filter((file) => path.extname(file) === '.json');
return fileNames.map((fileName) => {
@@ -21,9 +21,9 @@ export function getAllCategoryFiles(): Omit<Category, 'iconCount'>[] {
}
export function mapCategoryIconCount(
categories: Omit<Category, 'iconCount'>[],
categories: Category[],
icons: { categories: IconEntity['categories'] }[],
): Category[] {
) {
return categories.map((category) => ({
...category,
iconCount: icons.reduce(
@@ -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({
+1 -2
View File
@@ -1,5 +1,4 @@
import React from 'react';
import createLucideIcon from 'lucide-react/src/createLucideIcon';
import { createLucideIcon } from 'lucide-react/src/lucide-react';
import { type LucideProps, type IconNode } from 'lucide-react/src/types';
import { IconEntity } from '../theme/types';
import { renderToStaticMarkup } from 'react-dom/server';
-2
View File
@@ -20,8 +20,6 @@ export async function getData(name: string) {
const iconNode = iconNodes[name];
const releaseData = releaseMeta?.[name] ?? {
fromFork: false,
awaitingRelease: false,
createdRelease: {
version: '0.0.0',
date: DATE_OF_FORK,
-218
View File
@@ -1,218 +0,0 @@
import { readFileSync } from 'node:fs';
import { dirname, isAbsolute, resolve } from 'node:path';
import type { MarkdownIt, RenderRule } from './types';
import container from 'markdown-it-container';
type ContainerArgs = [typeof container, string, { render: RenderRule }];
type GuidanceType = 'do' | 'dont' | 'caution';
const isGuidanceOpenToken = (type: string) =>
type === 'container_do_open' ||
type === 'container_dont_open' ||
type === 'container_caution_open';
const isGuidanceCloseToken = (type: string) =>
type === 'container_do_close' ||
type === 'container_dont_close' ||
type === 'container_caution_close';
const parseInfo = (info: string, marker: string) => info.trim().slice(marker.length).trim();
const stripWrappingQuotes = (value: string) => {
const quote = value[0];
if ((quote === '"' || quote === "'") && value.at(-1) === quote) {
return value.slice(1, -1);
}
return value;
};
const isRawSvgUrl = (url: string) => {
const [filePath, query = ''] = url.split('?');
const params = new URLSearchParams(query);
return filePath.endsWith('.svg') && params.get('raw') === 'true';
};
const isImageUrl = (value: string) => {
const [filePath] = value.split('?');
return /\.(avif|gif|jpe?g|png|svg|webp)$/i.test(filePath);
};
const resolveMarkdownPath = (env: Record<string, unknown>) =>
(env.path as string | undefined) ??
(env.file as string | undefined) ??
(env.realPath as string | undefined);
const renderImage = (
url: string,
env: Record<string, unknown>,
escapeAttr: (value: string) => string,
) => {
if (!isRawSvgUrl(url)) {
return `<img class="example-guidance__image" src="${escapeAttr(url)}" alt="" loading="lazy">`;
}
const [filePath] = url.split('?');
const markdownPath = resolveMarkdownPath(env);
const resolvedPath = isAbsolute(filePath)
? resolve(process.cwd(), `.${filePath}`)
: resolve(markdownPath ? dirname(markdownPath) : process.cwd(), filePath);
const svg = readFileSync(resolvedPath, 'utf8');
const replacedSvg = svg
.replaceAll(/(#000(000)?|black)/g, 'currentColor')
.replaceAll(' stroke="#D8D8D9"', 'style="stroke: currentColor; stroke-opacity: .25;"');
return `<div class="example-guidance__image example-guidance__image--raw">${replacedSvg}</div>`;
};
const renderPreview = (
value: string,
env: Record<string, unknown>,
escapeHtml: (value: string) => string,
renderInline: (value: string) => string,
) => {
if (isImageUrl(value)) {
return renderImage(value, env, escapeHtml);
}
return `<div class="example-guidance__text">${renderInline(value)}</div>`;
};
const hasTopLevelContent = (tokens: Parameters<RenderRule>[0], index: number) => {
let guidanceDepth = 0;
for (let i = index + 1; i < tokens.length; i += 1) {
const token = tokens[i];
if (token.type === 'container_example_close') {
return false;
}
if (isGuidanceOpenToken(token.type)) {
guidanceDepth += 1;
continue;
}
if (isGuidanceCloseToken(token.type)) {
guidanceDepth -= 1;
continue;
}
if (guidanceDepth === 0) {
return true;
}
}
return false;
};
const hasGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
for (let i = index + 1; i < tokens.length; i += 1) {
const token = tokens[i];
if (token.type === 'container_example_close') {
return false;
}
if (isGuidanceOpenToken(token.type)) {
return true;
}
}
return false;
};
const isFirstGuidance = (tokens: Parameters<RenderRule>[0], index: number) => {
for (let i = index - 1; i >= 0; i -= 1) {
const token = tokens[i];
if (token.type === 'container_example_open') {
return true;
}
if (isGuidanceOpenToken(token.type)) {
return false;
}
}
return true;
};
const getExampleOpenIndex = (tokens: Parameters<RenderRule>[0], index: number) => {
for (let i = index; i >= 0; i -= 1) {
if (tokens[i].type === 'container_example_open') {
return i;
}
}
return index;
};
const labels: Record<GuidanceType, string> = {
do: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg> Do`,
dont: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg> Don't`,
caution: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-exclamation-point"><path d="M12 20 L12 20" /><path d="M12 4 L12 15" /></svg> Caution`,
};
export default function examplePlugin(md: MarkdownIt) {
const escapeHtml = md.utils.escapeHtml;
const createExampleContainer = (): ContainerArgs => [
container,
'example',
{
render(tokens, idx) {
const token = tokens[idx];
if (token.nesting === 1) {
const hasContent = hasTopLevelContent(tokens, idx);
const guidanceOnlyClass = hasContent ? '' : ' example-block--guidance-only';
const contentOpen = hasContent ? '<div class="example-block__content">\n' : '';
return `<section class="example-block${guidanceOnlyClass}">\n${contentOpen}`;
}
const exampleOpenIndex = getExampleOpenIndex(tokens, idx);
const contentClose =
hasTopLevelContent(tokens, exampleOpenIndex) && !hasGuidance(tokens, exampleOpenIndex)
? '</div>\n'
: '';
return `${contentClose}</section>\n`;
},
},
];
const createGuidanceContainer = (type: GuidanceType): ContainerArgs => [
container,
type,
{
render(tokens, idx, _options, env) {
const token = tokens[idx];
if (token.nesting === 1) {
const exampleOpenIndex = getExampleOpenIndex(tokens, idx);
const preview = stripWrappingQuotes(parseInfo(token.info, type));
const media = renderPreview(preview, env, escapeHtml, md.renderInline.bind(md));
const contentClose =
hasTopLevelContent(tokens, exampleOpenIndex) && isFirstGuidance(tokens, idx)
? '</div>\n'
: '';
const label = labels[type];
return `${contentClose}<figure class="example-guidance example-guidance--${type}">${media}<figcaption class="example-guidance__caption"><div class="example-guidance__label">${label}</div>\n`;
}
return '</figcaption></figure>\n';
},
},
];
md.use(...createExampleContainer());
md.use(...createGuidanceContainer('do'));
md.use(...createGuidanceContainer('dont'));
md.use(...createGuidanceContainer('caution'));
}
+3 -1
View File
@@ -1,4 +1,6 @@
import type { MarkdownIt, RenderRule, Token } from './types';
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';
+1 -1
View File
@@ -16,7 +16,7 @@
* />
*/
import type { MarkdownIt } from './types';
import type MarkdownIt from 'markdown-it';
type SnackParams = Record<string, string>;
-7
View File
@@ -1,7 +0,0 @@
import type { MarkdownRenderer } from 'vitepress';
// VitePress 1.x renders with markdown-it 14, so derive the plugin types from its renderer
// instead of the markdown-it 15 package installed in docs.
export type MarkdownIt = MarkdownRenderer;
export type RenderRule = NonNullable<MarkdownIt['renderer']['rules'][string]>;
export type Token = Parameters<RenderRule>[0][number];
+1 -1
View File
@@ -94,7 +94,7 @@ export const angularSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -69,7 +69,7 @@ export const astroSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
-1
View File
@@ -36,7 +36,6 @@ const sidebar: Sidebar = {
'/guide/react-native/': reactNativeSidebar,
'/guide/angular/': angularSidebar,
'/guide/static/': lucideStaticSidebar,
'/how-to': resourcesSidebar,
'/community': resourcesSidebar,
'/contribute': resourcesSidebar,
'/license': resourcesSidebar,
+1 -1
View File
@@ -79,7 +79,7 @@ export const lucideSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
],
+1 -1
View File
@@ -89,7 +89,7 @@ export const preactSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -100,7 +100,7 @@ export const reactSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+14 -83
View File
@@ -11,110 +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: 'How to use Lucide icons',
text: 'Designing icons',
items: [
{
text: 'Getting started',
link: '/how-to/',
text: 'Icon Design Principles',
link: '/contribute/icon-design-guide',
},
{
text: 'Accessibility guide',
link: '/how-to/accessibility',
desc: 'Learn how to make icons readable, understandable, and usable with assistive technology.',
text: 'Designing in Illustrator',
link: '/contribute/illustrator-guide',
},
{
text: 'How to pick the right icon',
link: '/how-to/icon-guide',
desc: 'Learn how to decide when icons belong in an interface and how to pick symbols people are likely to recognize.',
},
],
},
{
text: 'Contributing icons',
items: [
{
text: 'Getting started',
link: '/contribute/icons/',
text: 'Designing in Inkscape',
link: '/contribute/inkscape-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 Figma',
link: '/contribute/figma-guide',
},
{
text: 'Design specification',
link: '/contribute/icons/specification',
desc: 'Review the technical requirements every Lucide icon must meet.',
},
{
text: 'Naming conventions',
link: '/contribute/icons/naming-conventions',
desc: 'Learn how to choose clear, consistent, and predictable names for Lucide icons.',
},
{
text: 'Metadata conventions',
link: '/contribute/icons/metadata-conventions',
desc: 'Learn how to write use cases, tags, categories, aliases, and other icon metadata.',
items: [
{
text: 'Use case guide',
link: '/contribute/icons/metadata-conventions/use-case-guide',
desc: '',
},
{
text: 'Tag guide',
link: '/contribute/icons/metadata-conventions/tag-guide',
desc: '',
},
],
},
{
text: 'SVG conventions',
link: '/contribute/icons/code-conventions',
desc: 'Learn how to structure and write Lucide SVG files.',
},
{
text: 'Design software guides',
collapsed: false,
desc: 'Learn how to design and export Lucide icons with common vector tools.',
items: [
{
text: 'Adobe Illustrator',
link: '/contribute/icons/illustrator-guide',
desc: '',
},
{
text: 'Inkscape',
link: '/contribute/icons/inkscape-guide',
},
{
text: 'Figma',
link: '/contribute/icons/figma-guide',
},
{
text: 'Affinity Designer',
link: '/contribute/icons/affinity-designer-guide',
},
],
text: 'Designing in Affinity Designer',
link: '/contribute/affinity-designer-guide',
},
],
},
+1 -1
View File
@@ -90,7 +90,7 @@ export const solidSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -75,7 +75,7 @@ export const lucideStaticSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -79,7 +79,7 @@ export const svelteSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
+1 -1
View File
@@ -85,7 +85,7 @@ export const vueSidebar = [
items: [
{
text: 'Accessibility in depth',
link: '/how-to/accessibility',
link: '/guide/accessibility',
desc: 'Accessibility best practices',
},
{
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useLocalStorage } from '@vueuse/core';
import Button from './base/Button.vue';
import IconButton from './base/IconButton.vue';
import { x } from '../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import { computed, onMounted, ref } from 'vue';
@@ -65,17 +65,16 @@ function hidePopup() {
}"
class="ask-overlay"
>
<Button
<IconButton
@click="hidePopup"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</Button>
</IconButton>
<h2 class="title">
We value your feedback!
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { computed } from 'vue';
const props = defineProps<{
@@ -6,6 +7,8 @@ const props = defineProps<{
id: string;
}>();
const { isDark } = useData();
const emit = defineEmits(['update:modelValue']);
function limitHexInput(val: string) {
@@ -19,7 +22,7 @@ function limitHexInput(val: string) {
const value = computed({
get: () => {
if (props.modelValue == null || props.modelValue === 'currentColor') {
return null;
return isDark.value ? '#ffffff' : '#000000';
}
return props.modelValue;
@@ -1,38 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = defineProps<{
href?: string;
}>()
const isExternal = computed(() => {
return props.href?.startsWith('http') ?? false
})
const component = computed(() => {
return props.href ? 'a' : 'button'
})
const target = computed(() => {
return isExternal.value ? '_blank' : undefined
})
const rel = computed(() => {
return isExternal.value ? 'noreferrer noopener' : undefined
})
</script>
<template>
<component
:is="component"
v-bind="$attrs"
:href="href"
:target="target"
:rel="rel"
class="icon-button"
>
<button v-bind="$attrs" class="icon-button">
<slot />
</component>
</button>
</template>
<style scoped>
@@ -47,21 +16,12 @@ const rel = computed(() => {
transition: color 0.25s, border-color 0.25s, background-color 0.25s;
border-radius: 6px;
background-color: var(--vp-c-bg-alt);
color: inherit;
text-decoration: none;
/* width: 56px;
height: 56px; */
gap: 8px;
font-size: 16px;
font-weight: 500;
font-size: 24px;
transition: color 0.1s, border-color 0.1s, background-color 0.1s;
}
.icon-button span {
padding: 0 4px;
display: block;
}
.icon-button:hover {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
@@ -14,7 +14,7 @@ export interface InputProps {
import { ref, onMounted, nextTick, watch } from 'vue';
import Icon from '@lucide/vue/src/Icon';
import { x } from '../../../data/iconNodes';
import Button from './Button.vue';
import IconButton from './IconButton.vue';
const props = withDefaults(defineProps<InputProps>(), {
type: 'text',
@@ -68,9 +68,9 @@ defineExpose({
ref="input"
:value="modelValue"
v-bind="$attrs"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
@input="$emit('update:modelValue', $event.target.value)"
/>
<Button
<IconButton
@click="onClear"
v-if="type === 'search' && modelValue"
class="clear-button"
@@ -79,10 +79,9 @@ defineExpose({
>
<Icon
:iconNode="x"
name="x"
:size="20"
/>
</Button>
</IconButton>
<kbd
v-if="shortcut"
class="shortcut"
@@ -46,7 +46,6 @@ const value = computed({
<template #icon>
<Icon
:iconNode="search"
name="search"
class="search-icon"
/>
</template>
@@ -9,8 +9,7 @@ import RocketDuotoneIcon from '../home/RocketDuotoneIcon.ts';
<p>
<Icon
:iconNode="RocketDuotoneIcon"
name="rocket-duotone"
:size="18"
size="18"
class="rocket-icon"
/>
<VPLink
@@ -1,74 +0,0 @@
<script setup lang="ts">
import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue'
const props = defineProps<{
tabs: string[];
}>();
</script>
<template>
<TabGroup>
<TabList class="tab-list">
<template v-for="(tab, index) in props.tabs" :key="index">
<Tab :id="`tab-${index}`" class="tab">{{ tab }}</Tab>
</template>
</TabList>
<TabPanels>
<template v-for="(_, name) in $slots">
<TabPanel>
<slot :name="name" />
</TabPanel>
</template>
</TabPanels>
</TabGroup>
</template>
<style lang="css" scoped>
.tab-list {
display: flex;
gap: 8px;
margin-bottom: 32px;
border-bottom: 1px solid var(--vp-c-divider);
justify-content: center;
}
.tab {
display: block;
line-height: 32px;
font-size: 16px;
padding: 10px 12px;
font-weight: 500;
color: var(--vp-c-text-2);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.5s;
border-bottom: 2px solid transparent;
}
.tab[aria-selected="true"] {
color: var(--vp-c-text-1);
transition: color 0.25s;
border-bottom: 2px solid var(--vp-c-brand);
}
@media (min-width: 860px) {
.tab-list {
justify-content: flex-start;
}
.tab {
font-size: 18px;
padding: 10px 16px;
}
}
@media (min-width: 960px) {
.tab {
}
}
@media (min-width: 1152px) {
.tab {
}
}
</style>
@@ -34,7 +34,7 @@ const percentage = computed<string>(() => `${((Number(props.modelValue) - props.
type="range"
v-bind="$attrs"
v-bind:value="modelValue"
v-on:input="$emit('update:modelValue', Number(($event.target as HTMLInputElement).value))"
v-on:input="$emit('update:modelValue', Number($event.target.value))"
:min="min"
:max="max"
:step="step"
@@ -1,22 +1,20 @@
<script setup lang="ts">
import { rotateCw } from '../../../data/iconNodes';
import Icon from '@lucide/vue/src/Icon';
import Button from './Button.vue';
import IconButton from './IconButton.vue';
</script>
<template>
<Button class="reset-button" aria-label="Reset style">
<IconButton class="reset-button">
<Icon
:size="20"
:iconNode="rotateCw"
name="rotate-cw"
/>
</Button>
</IconButton>
</template>
<style scoped>
.reset-button {
transition: ease-in-out 0.1s opacity;
background: none;
padding: 0;
}
@@ -30,12 +28,6 @@ import Button from './Button.vue';
border-color: transparent;
}
.reset-button:disabled {
cursor: default;
pointer-events: none;
opacity: 0.5;
}
/* a rotate css animation keyframes */
@keyframes rotate {
0% {
@@ -27,7 +27,6 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
<slot name="end-icon">
<Icon
:iconNode="chevronsUpDown"
name="chevrons-up-down"
class="chevron-icon"
aria-hidden="true"
/>
@@ -58,7 +57,6 @@ const selected = defineModel<{ name: string; value: string; icon?: string; iconD
>
<Icon
:iconNode="check"
name="check"
class="check"
aria-hidden="true"
/>
@@ -1,6 +1,5 @@
<script setup>
import { Switch } from '@headlessui/vue'
import { onMounted, ref } from 'vue'
defineProps({
modelValue: {
@@ -10,19 +9,14 @@
})
const emit = defineEmits(['update:modelValue'])
const isHydrated = ref(false)
onMounted(() => {
isHydrated.value = true
})
</script>
<template>
<Switch
:model-value="isHydrated ? modelValue : false"
:model-value="modelValue"
@update:model-value="emit('update:modelValue', $event)"
class="switch"
:class="{ enabled: isHydrated && modelValue }"
:class="{ enabled: modelValue }"
>
<span class="thumb" />
</Switch>
@@ -1,11 +1,13 @@
<script lang="ts" setup>
import { Sandpack, type SandpackProps } from 'sandpack-vue3';
import { type FunctionalComponent } from 'vue';
import { type LucideProps } from '@lucide/vue';
import { Sandpack } from 'sandpack-vue3';
const props = defineProps<SandpackProps>();
defineProps<FunctionalComponent<LucideProps>>();
</script>
<template>
<Sandpack v-bind="props" />
<Sandpack />
</template>
<style>
@@ -10,14 +10,7 @@ import { useLocalStorage } from '@vueuse/core';
const { page } = useData();
const router = useRouter();
interface Framework {
name: string;
icon: string;
iconDark?: string;
value: string;
}
const frameworks: Framework[] = [
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/' },
@@ -55,7 +48,7 @@ const selected = computed(() => {
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
});
function onSelectFramework(item: Framework) {
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
fallbackFramework.value = item;
if (item.value !== router.route.path) {
const likeRoute = router.route.path.replace(selected.value.value, item.value);
@@ -2,12 +2,9 @@
import { ref, shallowRef } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import { useRouter } from 'vitepress';
import { motion, useScroll, useTransform, type Options as MotionOptions } from 'motion-v';
import { motion, Variants, useScroll, useTransform } from 'motion-v';
import LucideIcon from '../base/LucideIcon.vue';
// motion-v re-exports framer-motion's `Variants`, which doesn't match the `variants` prop of its components
type Variants = MotionOptions['variants'];
const emit = defineEmits(['animation-complete']);
const MotionLucideIcon = motion.create(LucideIcon);
@@ -132,7 +129,7 @@ const onAnimationComplete = (item) => {
const randomIndex = ref(Math.floor(Math.random() * 64));
const iconAnimationVariants: Variants = {
const iconAnimationVariants = {
initial: {
animationName: 'end',
opacity: 0,
@@ -152,7 +149,7 @@ const iconAnimationVariants: Variants = {
const shrinkIconAnimation = ref('initial');
const shrinkIconVariants: Variants = {
const shrinkIconVariants = {
initial: { strokeWidth: 2 },
shrinkIcons: (index) => ({
animationName: 'shrinkIcons',
@@ -18,7 +18,7 @@ const pickIcons = [
'clapperboard',
'tool-case',
'printer',
'square-bookmark',
'album',
'pencil-ruler',
'calendar',
'brain',
@@ -53,7 +53,7 @@ const pickIcons = [
'terminal',
'thermometer',
'thumbs-up',
'trash',
'trash-2',
'umbrella',
'watch',
'sparkles',
@@ -69,7 +69,7 @@ const pickIcons = [
'stethoscope',
];
const loader = {
export default {
async load() {
const icons = Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode }));
@@ -96,8 +96,3 @@ const loader = {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -1,4 +1,4 @@
const loader = {
export default {
async load() {
const version = await fetch('https://api.github.com/repos/lucide-icons/lucide/releases/latest')
.then((res) => {
@@ -16,8 +16,3 @@ const loader = {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -15,11 +15,10 @@ import RocketDuotoneIcon from './RocketDuotoneIcon.ts';
class="badge-special confetti-button animate"
href="/guide/version-1"
>
<Icon :iconNode="RocketDuotoneIcon" name="rocket-duotone" />
<Icon :iconNode="RocketDuotoneIcon" />
Version 1
<Icon
:iconNode="moveRight"
name="move-right"
class=""
/>
</Badge>
@@ -1,21 +1,59 @@
<script setup lang="ts">
import { computed } from 'vue';
import { useIconStyle } from '../../composables/useIconStyle';
import HomeContainer from './HomeContainer.vue';
import RangeSlider from '../base/RangeSlider.vue';
import InputField from '../base/InputField.vue';
import ColorPicker from '../base/ColorPicker.vue';
import ResetButton from '../base/ResetButton.vue';
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue';
import Switch from '../base/Switch.vue';
import { ref, watch } from 'vue'
import { syncRef, useCssVar } from '@vueuse/core'
import HomeContainer from './HomeContainer.vue'
import RangeSlider from '../base/RangeSlider.vue'
import InputField from '../base/InputField.vue'
import ColorPicker from '../base/ColorPicker.vue'
import ResetButton from '../base/ResetButton.vue'
import HomeIconCustomizerIcons from './HomeIconCustomizerIcons.vue'
import Switch from '../base/Switch.vue'
const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle } = useIconStyle();
const iconStyle = computed(() => ({
'--home-icon-color': color.value,
'--home-icon-stroke-width': String(strokeWidth.value),
'--home-icon-size': String(size.value),
}));
const iconContainer = ref<HTMLElement | null>()
const color = ref('currentColor')
const strokeWidth = ref(2)
const size = ref(24)
const absoluteStrokeWidth = ref(false)
const colorCssVar = useCssVar(
'--customize-color',
iconContainer,
{
initialValue: 'default'
}
)
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
iconContainer,
{
initialValue: '2'
}
)
const sizeCssVar = useCssVar(
'--customize-size',
iconContainer,
{
initialValue: '24'
}
)
syncRef(color, colorCssVar)
syncRef(strokeWidth, strokeWidthCssVar)
syncRef(size, sizeCssVar)
function resetStyle () {
color.value = 'currentColor'
strokeWidth.value = 2
size.value = 24
absoluteStrokeWidth.value = false
}
watch(absoluteStrokeWidth, (enabled) => {
iconContainer.value?.classList.toggle('absolute-stroke-width', enabled)
})
</script>
<template>
@@ -24,10 +62,7 @@ const iconStyle = computed(() => ({
<div class="card-column">
<h2 class="title">
Style as you please
<ResetButton
:disabled="!isCustomized"
@click="resetStyle"
/>
<ResetButton @click="resetStyle"></ResetButton>
</h2>
<p class="copy">
Lucide has a lot of customization options to match the icons with your UI.
@@ -43,10 +78,7 @@ const iconStyle = computed(() => ({
class="color-picker-field"
>
<template #display>
<ColorPicker
v-model="color"
id="icon-color-picker"
/>
<ColorPicker v-model="color" id="icon-color" />
</template>
</InputField>
@@ -58,7 +90,7 @@ const iconStyle = computed(() => ({
<span class="customize-label">{{ strokeWidth }}px</span>
</template>
<RangeSlider
id="stroke-width-slider"
id="stroke-width"
name="stroke-width"
v-model="strokeWidth"
:min="1"
@@ -75,7 +107,7 @@ const iconStyle = computed(() => ({
<span class="customize-label">{{ size }}px</span>
</template>
<RangeSlider
id="size-slider"
id="size"
name="size"
v-model="size"
:min="16"
@@ -90,7 +122,7 @@ const iconStyle = computed(() => ({
>
<template #display>
<Switch
id="absolute-stroke-width-switch"
id="absolute-stroke-width"
name="absolute-stroke-width"
v-model="absoluteStrokeWidth"
/>
@@ -99,11 +131,8 @@ const iconStyle = computed(() => ({
</div>
</div>
<div class="icons-container card-column">
<HomeIconCustomizerIcons
:class="{ 'absolute-stroke-width': absoluteStrokeWidth }"
:style="iconStyle"
/>
<div class="icons-container card-column" ref="iconContainer">
<HomeIconCustomizerIcons />
</div>
</div>
</HomeContainer>
@@ -150,6 +179,10 @@ const iconStyle = computed(() => ({
display: grid;
grid-template-columns: 8fr 10fr;
}
/*
.card-column {
flex: 1;
} */
}
@media (min-width: 960px) {
@@ -158,7 +191,7 @@ const iconStyle = computed(() => ({
}
}
.color-picker-field:deep(.icon-color-picker-input) {
width: 116px;
.color-picker-field:deep(.display-value) {
width: 138px;
}
</style>
@@ -1,35 +1,29 @@
<script setup lang="ts">
import { ref } from 'vue';
import { data } from './HomeHeroIconsCard.data';
import LucideIcon from '../base/LucideIcon.vue';
import { vIntersectionObserver } from '@vueuse/components';
import { data } from './HomeHeroIconsCard.data'
import LucideIcon from '../base/LucideIcon.vue'
import { vIntersectionObserver } from '@vueuse/components'
const getInitialItems = () => data.icons.slice(0, 64);
const items = ref(getInitialItems());
const showIcons = ref(false);
const getInitialItems = () => data.icons.slice(0, 64)
const items = ref(getInitialItems())
const showIcons = ref(false)
// Added intersection observer to improve performance
const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting }]) => {
if (isIntersecting) {
showIcons.value = true;
showIcons.value = true
}
};
}
</script>
<template>
<div
class="icon-grid"
v-intersection-observer="onIntersectionObserver"
>
<div class="icon-grid" v-intersection-observer="onIntersectionObserver">
<template v-if="showIcons">
<div
v-for="icon in items"
class="icon-grid-item"
>
<LucideIcon
v-bind="icon"
class="lucide-icon"
/>
>
<LucideIcon v-bind="icon" class="lucide-icon"/>
</div>
</template>
</div>
@@ -42,7 +36,7 @@ const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting
grid-template-columns: repeat(auto-fill, minmax(68px, 1fr));
grid-template-rows: repeat(auto-fill, minmax(68px, 1fr));
width: 100%;
height: 100%;
height:100%;
max-height: 360px;
gap: 1px;
overflow: hidden;
@@ -71,15 +65,13 @@ const onIntersectionObserver: IntersectionObserverCallback = ([{ isIntersecting
.lucide-icon {
will-change: width, height, stroke-width, stroke;
color: var(--home-icon-color, currentColor);
stroke-width: var(--home-icon-stroke-width, 2);
width: calc(var(--home-icon-size, 24) * 1px);
height: calc(var(--home-icon-size, 24) * 1px);
max-width: 3rem;
max-height: 3rem;
color: var(--customize-color, currentColor);
stroke-width: var(--customize-strokeWidth, 2);
width: calc(var(--customize-size, 24) * 1px);
height: calc(var(--customize-size, 24) * 1px);
}
.icon-grid.absolute-stroke-width .lucide-icon {
stroke-width: calc(var(--home-icon-stroke-width, 2) * 24 / var(--home-icon-size, 24));
.icons-container.absolute-stroke-width .lucide-icon {
stroke-width: calc(var(--customize-strokeWidth, 2) * 24 / var(--customize-size, 24));
}
</style>
@@ -1,4 +1,4 @@
const loader = {
export default {
async load() {
return {
packages: [
@@ -61,8 +61,3 @@ const loader = {
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { computed } from 'vue';
const props = defineProps<{
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
headingLevel: 1 | 2 | 3 | 4 | 5 | 6,
}>()
const headingElement = computed(() => `h${props.headingLevel ?? 2}`)
@@ -1,6 +1,4 @@
import type { LucideIconNode } from '@lucide/vue/src/types';
const RocketDuotoneIcon: LucideIconNode[] = [
const RocketDuotoneIcon = [
[
'path',
{
@@ -18,6 +16,6 @@ const RocketDuotoneIcon: LucideIconNode[] = [
'clip-rule': 'evenodd',
},
],
];
] as const;
export default RocketDuotoneIcon;
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { useData } from 'vitepress';
import { useSessionStorage } from '@vueuse/core';
import Button from '../base/Button.vue';
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';
@@ -40,17 +40,16 @@ onMounted(() => {
class="floating-ad"
v-if="theme.carbonAds"
>
<Button
<IconButton
@click="hideAd"
class="hide-button"
>
<Icon
:iconNode="x"
name="x"
:size="20"
absoluteStrokeWidth
/>
</Button>
</IconButton>
<VPDocAsideCarbonAds :carbon-ads="theme.carbonAds" overlay />
</div>
</template>
@@ -1,17 +1,14 @@
import { getAllCategoryFiles, mapCategoryIconCount } from '../../../lib/categories';
import iconsMetaData from '../../../data/iconMetaData';
const loader = {
export default {
async load() {
const categories = mapCategoryIconCount(getAllCategoryFiles(), Object.values(iconsMetaData));
let categories = getAllCategoryFiles();
categories = mapCategoryIconCount(categories, Object.values(iconsMetaData));
return {
categories,
};
},
};
declare const data: Awaited<ReturnType<typeof loader.load>>;
export { data };
export default loader;
@@ -2,7 +2,7 @@
import { ref, computed, watch } from 'vue'
import { useData } from 'vitepress'
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue'
import { isActive } from 'vitepress/dist/client/shared.js'
import { isActive } from 'vitepress/dist/client/shared'
import { useActiveAnchor } from '../../composables/useActiveAnchor'
import { data } from './CategoryList.data'
import CategoryListItem from './CategoryListItem.vue'
@@ -1,29 +0,0 @@
<script setup lang="ts">
import { pencilSparkles } from '../../../data/iconNodes';
import { IconEntity } from '../../types';
import getStudioLink from '../../utils/getStudioLink';
import Button from '../base/Button.vue';
import Icon from '@lucide/vue/src/Icon.ts';
defineProps<{
icon: IconEntity
}>()
</script>
<template>
<Button
:href="getStudioLink(icon.name, icon.iconNode, 'icon-info')"
target="_blank"
rel="noopener noreferrer"
>
<Icon :iconNode="pencilSparkles" name="pencil-sparkles" :size="24" />
<span class="text">Edit in studio</span>
</Button>
</template>
<style scoped>
.text {
margin: 0 4px;
}
</style>
@@ -30,15 +30,10 @@ const props = defineProps<{
.contributors {
display: flex;
flex-direction: row;
align-items: start;
align-items: center;
/* justify-content: flex-end; */
gap: 2px;
gap: 16px;
}
.contributors .label {
margin-top: 12px;
}
.avatar-group {
display: flex;
}
@@ -16,17 +16,19 @@ const tags = computed(() => {
</script>
<template>
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
<!-- <PageContainer class="overview"> -->
<IconPreview
:name="$params.name"
:iconNode="$params.iconNode"
class="preview"
customizable
/>
<div class="details">
<h1 class="title">
{{ $params.name }}
</h1>
</div>
<!-- </PageContainer> -->
</template>
<style scoped>
@@ -6,11 +6,7 @@ import useConfetti from '../../composables/useConfetti';
const { animate, confetti } = useConfetti();
const slots = useSlots();
const copiedText = computed(() => {
const children = slots.default?.()[0].children;
return typeof children === 'string' ? children : '';
});
const copiedText = computed(() => slots.default?.()[0].children);
function copyText() {
navigator.clipboard.writeText(copiedText.value);
@@ -29,7 +25,6 @@ function copyText() {
<slot />
<Icon
:iconNode="copy"
name="copy"
:size="20"
class="copy-icon"
/>
@@ -2,7 +2,7 @@
import type { IconEntity } from '../../types';
import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import Button from '../base/Button.vue';
import IconButton from '../base/IconButton.vue';
import IconContributors from './IconContributors.vue';
import IconPreview from './IconPreview.vue';
import { x, expand } from '../../../data/iconNodes';
@@ -12,7 +12,6 @@ import Badge from '../base/Badge.vue';
import { computedAsync } from '@vueuse/core';
import { satisfies } from 'semver';
import { useExternalLibs } from '../../composables/useExternalLibs';
import EditInStudioButton from './EditInStudioButton.vue';
const props = defineProps<{
iconName: string | null;
@@ -71,19 +70,12 @@ const Expand = createLucideIcon('Expand', expand);
<div class="overlay-panel">
<nav class="overlay-menu">
<Badge
v-if="icon.awaitingRelease"
class="version awaiting-release"
>Unreleased</Badge
>
<Badge
v-else-if="icon.createdRelease"
v-if="icon.createdRelease"
class="version"
:href="releaseTagLink(icon.createdRelease.version)"
>v{{ icon.createdRelease.version }}</Badge
>
{{ icon.createdRelease.version }}
</Badge>
<EditInStudioButton :icon="icon"/>
<Button
<IconButton
@click="
go(
icon.externalLibrary
@@ -93,10 +85,10 @@ const Expand = createLucideIcon('Expand', expand);
"
>
<component :is="Expand" />
</Button>
<Button @click="onClose">
</IconButton>
<IconButton @click="onClose">
<component :is="CloseIcon" />
</Button>
</IconButton>
</nav>
<IconPreview
id="previewer"
@@ -208,14 +200,7 @@ const Expand = createLucideIcon('Expand', expand);
margin-right: 24px;
}
.version.awaiting-release {
color: var(--vp-c-danger-3);
}
.contributors {
justify-content: flex-end;
position: absolute;
bottom: 24px;
right: 24px;
}
</style>
@@ -10,36 +10,30 @@ import { computed } from 'vue';
import createLucideIcon from '@lucide/vue/src/createLucideIcon';
import { diamond } from '../../../data/iconNodes';
import deprecationReasonTemplate from '../../../../../tools/build-icons/utils/deprecationReasonTemplate.ts';
import type { IconDeprecationReason } from '../../../../../tools/build-icons/types.ts';
import IconTags from "./IconTags.vue"
import getStudioLink from '../../utils/getStudioLink';
const props = defineProps<{
icon: IconEntity;
popoverPosition?: 'top' | 'bottom';
showEditButton?: boolean;
}>();
const { go } = useRouter();
const { page } = useData();
const tags = computed(() => {
if (!props.icon || !props?.icon?.tags) return [];
return props.icon.tags.join(' • ');
});
const DiamondIcon = createLucideIcon('Diamond', diamond);
const deprecatedTitle = computed(() => {
if (!props.icon.deprecationReason) return '';
return deprecationReasonTemplate(props.icon.deprecationReason as IconDeprecationReason, {
return deprecationReasonTemplate(props.icon.deprecationReason, {
componentName: props.icon.name,
iconName: props.icon.name,
toBeRemovedInVersion: props.icon.toBeRemovedInVersion,
});
});
const iconPath = computed(() => {
if (props.icon.externalLibrary) {
return `icons/${props.icon.externalLibrary}/${props.icon.name}`;
}
return `icons/${props.icon.name}`;
});
</script>
<template>
@@ -66,7 +60,14 @@ const iconPath = computed(() => {
Deprecated
</Badge>
</div>
<IconTags :tags="props.icon.tags" />
<div
class="tags-scroller"
v-if="tags.length"
>
<p class="icon-tags horizontal-scroller">
{{ tags }}
</p>
</div>
<div class="group">
<Badge
v-for="category in icon.categories"
@@ -79,10 +80,26 @@ const iconPath = computed(() => {
<div class="group buttons">
<VPButton
v-if="!page?.relativePath?.startsWith?.(iconPath)"
:href="iconPath"
v-if="
!page?.relativePath?.startsWith?.(
icon.externalLibrary
? `icons/${icon.externalLibrary}/${icon.name}`
: `icons/${icon.name}`,
)
"
:href="
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`
"
text="See in action"
@click="go(iconPath)"
@click="
go(
icon.externalLibrary
? `/icons/${icon.externalLibrary}/${icon.name}`
: `/icons/${icon.name}`,
)
"
/>
<CopySVGButton
:name="icon.name"
@@ -98,38 +115,6 @@ const iconPath = computed(() => {
</template>
<style scoped>
.studio-edit-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 14px;
min-height: 40px;
border: 1px solid var(--vp-button-alt-border);
border-radius: 999px;
background-color: var(--vp-button-alt-bg);
color: var(--vp-button-alt-text);
font-size: 15px;
font-weight: 600;
line-height: 1;
text-decoration: none;
transition:
border-color 0.2s,
color 0.2s,
background-color 0.2s,
transform 0.2s;
margin-left: 0;
margin-inline-start: 0;
}
.studio-edit-button:hover,
.studio-edit-button:focus-visible {
border-color: var(--vp-button-alt-hover-border);
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
transform: translateY(-1px);
}
.group {
display: flex;
flex-wrap: wrap;
@@ -171,6 +156,66 @@ const iconPath = computed(() => {
background-color: var(--vp-c-brand-2);
}
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
white-space: nowrap;
position: absolute;
top: 0;
left: 0;
right: 0;
margin-top: 0;
margin-bottom: 0;
}
.tags-scroller {
position: relative;
max-width: 100%;
width: 100%;
height: 28px;
padding: 8px 0 16px;
margin-bottom: 16px;
margin-top: 8px;
align-items: center;
--gradient-background: var(--tags-gradient-background, var(--vp-c-bg-elv));
}
.horizontal-scroller {
overflow-x: scroll;
/* Hide Scrollbar */
-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 */
}
.horizontal-scroller::-webkit-scrollbar {
width: 0;
display: none;
}
.horizontal-scroller::-webkit-scrollbar-track {
background: transparent;
}
.horizontal-scroller::-webkit-scrollbar-thumb {
background: transparent;
border: none;
}
.tags-scroller::after {
content: '';
position: absolute;
bottom: 0;
width: 32px;
height: 100%;
/* Background Gradient left to right */
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, var(--gradient-background) 100%);
right: 0;
pointer-events: none;
}
.buttons {
margin-top: 24px;
}
@@ -3,6 +3,7 @@ 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'];
@@ -10,8 +11,6 @@ const props = defineProps<{
customizable?: boolean;
}>();
const activeSize = ref('1000%');
const { size, color, strokeWidth, absoluteStrokeWidth } = useIconStyleContext();
const previewIcon = ref();
@@ -21,6 +20,31 @@ 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>
@@ -31,8 +55,6 @@ const iconComponent = computed(() => {
:size="size"
:color="color"
:strokeWidth="absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth"
class="preview-icon"
:data-size="activeSize"
/>
<svg
class="icon-grid"
@@ -40,7 +62,6 @@ const iconComponent = computed(() => {
fill="none"
stroke-width="0.1"
xmlns="http://www.w3.org/2000/svg"
:class="{ show: activeSize === '1000%' }"
>
<g
:key="`grid-${i}`"
@@ -62,17 +83,16 @@ const iconComponent = computed(() => {
/>
</g>
</svg>
<div class="size-buttons" :class="{ show: activeSize !== '1000%' }">
<button class="size-button" @click="activeSize = '1000%'" :class="{ active: activeSize === '1000%' }">
1000%
</button>
<button class="size-button" @click="activeSize = '200%'" :class="{ active: activeSize === '200%' }">
200%
</button>
<button class="size-button" @click="activeSize = '100%'" :class="{ active: activeSize === '100%' }">
100%
</button>
</div>
<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>
@@ -84,87 +104,56 @@ const iconComponent = computed(() => {
top: 0;
left: 0;
stroke: var(--vp-c-divider);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.icon-grid.show {
opacity: 1;
}
.icon-container {
height: 100%;
aspect-ratio: 1/1;
position: relative;
background: var(--vp-c-bg-alt);
border-radius: 8px;
display: flex;
overflow: hidden;
flex-shrink: 0;
}
.icon-container:hover .size-buttons {
opacity: 1;
}
.icon-container > :deep(svg.preview-icon) {
.icon-container > :deep(svg:not(.icon-grid)) {
width: 100%;
height: 100%;
position: relative;
z-index: 1;
color: var(--vp-c-neutral);
opacity: 0.8;
transition: all 0.3s;
margin: auto;
}
.icon-container > :deep(svg.preview-icon[data-size='1000%']) {
width: 100%;
height: 100%;
.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 > :deep(svg.preview-icon[data-size='200%']) {
width: 48px;
height: 48px;
.icon-container:hover .studio-button,
.studio-button:focus-visible {
opacity: 1;
}
.icon-container > :deep(svg.preview-icon[data-size='100%']) {
width: 24px;
height: 24px;
.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;
}
.size-buttons {
position: absolute;
bottom: 8px;
left: 50%;
padding: 4px;
border-radius: 12px;
transform: translateX(-50%);
display: flex;
z-index: 99;
background: var(--vp-code-tab-bg);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
.size-buttons.show {
opacity: 1;
}
.size-button {
color: var(--vp-code-tab-text-color);
padding: 2px 8px;
}
.size-button.active {
background: var(--vp-c-neutral-inverse);
box-shadow: var(--vp-shadow-1);
border-radius: 4px;
color: var(--vp-code-tab-active-text-color);
text-decoration: none;
/* color: var(--customize-color, currentColor);
stroke-width: var(--customize-strokeWidth, 2); */
}
</style>
@@ -70,6 +70,7 @@ const prettyName = props.name
class="showcase"
v-if="iconComponent"
>
<h2 class="title">See this icon in action</h2>
<div class="showcase-grid">
<div class="showcase-item column">
<div class="placeholder"></div>
@@ -235,17 +236,18 @@ const prettyName = props.name
.showcase-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 32px;
background: var(--vp-code-block-bg);
padding: 32px;
border-radius: 8px;
gap: 48px;
margin-inline-start: 24px;
margin-block-start: 48px;
}
.showcase-item {
padding: 24px;
border-radius: 8px;
background-color: var(--vp-c-bg);
box-shadow: var(--vp-shadow-2);
box-shadow:
var(--vp-shadow-4),
-24px -24px 0 var(--vp-c-bg-soft);
display: flex;
align-items: center;
justify-content: center;
@@ -1,110 +0,0 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
const MAX_VISIBLE_TAGS = 5;
const props = defineProps<{
tags?: string[];
}>();
const tags = computed(() => props.tags ?? []);
const expanded = ref(false);
const hiddenCount = computed(() => Math.max(tags.value.length - MAX_VISIBLE_TAGS, 0));
const showAll = computed(() => expanded.value || hiddenCount.value === 0);
// The overlay reuses this component when switching icons, so collapse again
watch(tags, () => {
expanded.value = false;
});
</script>
<template>
<ul
v-if="tags.length"
class="icon-tags"
>
<!-- v-show keeps hidden tags in the rendered HTML so crawlers can index them -->
<li
v-for="(tag, index) in tags"
v-show="showAll || index < MAX_VISIBLE_TAGS"
:key="tag"
class="tag"
>
{{ tag }}
</li>
<li
v-if="!showAll"
class="more"
>
<button
type="button"
class="more-button"
:aria-label="`Show ${hiddenCount} more tags`"
@click="expanded = true"
>
+{{ hiddenCount }}
</button>
</li>
</ul>
</template>
<style scoped>
.icon-tags {
font-size: 16px;
color: var(--vp-c-text-2);
font-weight: 500;
line-height: 28px;
display: flex;
flex-wrap: wrap;
align-items: center;
list-style-type: disc;
gap: 4px 8px;
/* Clip bullets hanging before tags that start a wrapped line, padding leaves room for the focus ring */
overflow: hidden;
padding: 4px;
margin: 4px -4px 12px;
}
.tag {
margin-top: 0;
padding-left: 0px;
padding-right: 12px;
list-style-position: outside;
white-space: nowrap;
}
.tag:first-child {
list-style: none;
margin-left: 0;
padding-left: 0;
}
.more {
margin-top: 0;
list-style: none;
}
.more-button {
font: inherit;
color: var(--vp-c-text-2);
font-size: 12px;
padding: 0 8px;
margin-left: -4px;
border-radius: 16px;
background-color: var(--vp-c-bg-alt);
transition:
color 0.25s,
background-color 0.25s;
}
.more-button:hover,
.more-button:focus-visible {
color: var(--vp-c-text-1);
background-color: var(--vp-c-default-soft);
}
.more-button:focus-visible {
outline: 2px solid var(--vp-c-brand-1);
outline-offset: 2px;
}
</style>
@@ -122,7 +122,7 @@ const categoriesList = computed(() => {
});
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
() => searchQuery.value.length > 0 && (tags.value == null || categoriesMap.value == null),
);
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
@@ -163,7 +163,7 @@ const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location.href);
const url = new URL(window.location);
url.pathname = '/icons/categories';
if (searchQueryDebounced.value) {
@@ -35,6 +35,7 @@ const SORTING = [
},
]
const initialGridItems = computed(() => {
if (containerWidth.value === 0) return 120;
@@ -71,8 +72,6 @@ const sortedIcons = computed(() => {
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
case 'release-date':
return [...props.icons].sort((a, b) => {
if (a.awaitingRelease !== b.awaitingRelease) return a.awaitingRelease ? -1 : 1;
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;
@@ -89,13 +88,9 @@ const mappedIcons = computed(() => {
return sortedIcons.value;
}
if (categories.value == null) {
return sortedIcons.value;
}
return sortedIcons.value.map((icon) => {
const iconTags = tags.value[icon.name];
const iconCategories = categories.value[icon.name] ?? [];
const iconCategories = categories.value?.[icon.name] ?? [];
return {
...icon,
@@ -120,7 +115,7 @@ const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
const isSearchMetadataLoading = computed(
() => searchQuery.value.length > 0 && !isFetchingTags && !isFetchingCategories,
() => searchQuery.value.length > 0 && (tags.value == null || categories.value == null),
);
const chunkedIcons = computed(() => {
@@ -173,7 +168,7 @@ watch(searchQueryDebounced, () => {
function handleCloseDrawer() {
setActiveIconName('');
const url = new URL(window.location.href);
const url = new URL(window.location);
url.pathname = '/icons/';
if (searchQueryDebounced.value) {
@@ -208,7 +203,6 @@ function handleCloseDrawer() {
<template #start-icon>
<Icon
:iconNode="listSortDescending"
name="list-sort-descending"
class="chevron-icon"
aria-hidden="true"
/>
@@ -39,7 +39,7 @@ interface Placeholder {
finePrint?: string;
}
const brandPlaceholders: readonly Placeholder[] = shallowReadonly([
const brandPlaceholders: Placeholder[] = shallowReadonly([
{
title: 'Boooo! What a scary brand logo!',
message:
@@ -103,7 +103,7 @@ const brandPlaceholders: readonly Placeholder[] = shallowReadonly([
},
]);
const notFoundPlaceholders: readonly Omit<Placeholder, 'title'>[] = shallowReadonly([
const notFoundPlaceholders: Omit<Placeholder, 'title'>[] = shallowReadonly([
{
message: 'We’ve looked for this icon for a bird’s eye view, but could not find it.',
icon: markRaw(bird),
@@ -130,7 +130,7 @@ const notFoundPlaceholders: readonly Omit<Placeholder, 'title'>[] = shallowReado
},
]);
function randomItem<T>(arr: readonly T[]): T {
function randomItem<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
@@ -9,6 +9,7 @@ defineProps<{
<template>
<section class="related-icons">
<h2 class="title">More icons like this</h2>
<IconGrid :icons="icons" />
</section>
</template>
@@ -1,26 +1,73 @@
<script setup lang="ts">
import { useIconStyleContext } from '../../composables/useIconStyle';
import { shallowRef, type Ref, watch, computed } from 'vue';
import { useCssVar, syncRef } from '@vueuse/core';
import { STYLE_DEFAULTS, useIconStyleContext } from '../../composables/useIconStyle';
import RangeSlider from '../base/RangeSlider.vue';
import InputField from '../base/InputField.vue';
import ColorPicker from '../base/ColorPicker.vue';
import ResetButton from '../base/ResetButton.vue';
import Switch from '../base/Switch.vue';
const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle } =
useIconStyleContext();
const props = defineProps<{
rootEl?: Ref<HTMLElement>;
}>();
const { color, strokeWidth, size, absoluteStrokeWidth } = useIconStyleContext();
const documentRef = shallowRef<HTMLElement | undefined>(
typeof document !== 'undefined' ? document?.documentElement : undefined,
);
const colorCssVar = useCssVar('--customize-color', props.rootEl?.value ?? documentRef.value, {
initialValue: `${STYLE_DEFAULTS.color}`,
});
const strokeWidthCssVar = useCssVar(
'--customize-strokeWidth',
props.rootEl?.value ?? documentRef.value,
{
initialValue: `${STYLE_DEFAULTS.strokeWidth}`,
},
);
const sizeCssVar = useCssVar('--customize-size', props.rootEl?.value ?? documentRef.value, {
initialValue: `${STYLE_DEFAULTS.size}`,
});
syncRef(color, colorCssVar, { direction: 'ltr' });
syncRef(strokeWidth, strokeWidthCssVar, { direction: 'ltr' });
syncRef(size, sizeCssVar, { direction: 'ltr' });
function resetStyle() {
color.value = STYLE_DEFAULTS.color;
strokeWidth.value = STYLE_DEFAULTS.strokeWidth;
size.value = STYLE_DEFAULTS.size;
absoluteStrokeWidth.value = STYLE_DEFAULTS.absoluteStrokeWidth;
}
watch(absoluteStrokeWidth, (enabled) => {
const htmlEl = document.documentElement;
htmlEl.classList.toggle('absolute-stroke-width', enabled);
});
const customizingActive = computed(() => {
return (
color.value !== STYLE_DEFAULTS.color ||
strokeWidth.value !== STYLE_DEFAULTS.strokeWidth ||
size.value !== STYLE_DEFAULTS.size ||
absoluteStrokeWidth.value !== STYLE_DEFAULTS.absoluteStrokeWidth
);
});
</script>
<template>
<div
class="customizer-card"
:class="{ customized: isCustomized }"
:class="{ customized: customizingActive }"
>
<div class="card-header">
<h2 class="card-title">Customizer</h2>
<ResetButton
:disabled="!isCustomized"
@click="resetStyle"
/>
<ResetButton @click="resetStyle"></ResetButton>
</div>
<InputField
id="icon-color"
@@ -28,7 +75,7 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
>
<ColorPicker
v-model="color"
id="icon-color-picker"
id="icon-color"
class="color-picker"
/>
</InputField>
@@ -41,7 +88,7 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
<span class="customize-label">{{ strokeWidth }}px</span>
</template>
<RangeSlider
id="stroke-width-slider"
id="stroke-width"
name="stroke-width"
v-model="strokeWidth"
:min="0.5"
@@ -58,7 +105,7 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
<span class="customize-label">{{ size }}px</span>
</template>
<RangeSlider
id="size-slider"
id="size"
name="size"
v-model="size"
:min="16"
@@ -72,7 +119,7 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
label="Absolute stroke width"
>
<Switch
id="absolute-stroke-width-switch"
id="absolute-stroke-width"
name="absolute-stroke-width"
v-model="absoluteStrokeWidth"
/>
@@ -93,6 +140,7 @@ const { color, strokeWidth, size, absoluteStrokeWidth, isCustomized, resetStyle
color: var(--vp-c-text-1);
line-height: 32px;
font-size: 16px;
/* margin-bottom: 12px; */
}
.customizer-card {

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