mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-10 13:18:25 +00:00
Compare commits
110
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80a2d2540d | ||
|
|
658573b017 | ||
|
|
5136572c10 | ||
|
|
4f8088fd90 | ||
|
|
ae40cfbc73 | ||
|
|
cdd7db1ce6 | ||
|
|
f930bbe07c | ||
|
|
f229f83f0c | ||
|
|
b442632ee6 | ||
|
|
078a0b2339 | ||
|
|
5d78605650 | ||
|
|
ab61fa277b | ||
|
|
bb1329e512 | ||
|
|
b5b5d95933 | ||
|
|
7b589f2b5c | ||
|
|
2d36863568 | ||
|
|
7291cbcc0f | ||
|
|
4c663b9a32 | ||
|
|
63369b1ac0 | ||
|
|
f07465f82e | ||
|
|
60d583ce4e | ||
|
|
5828ecb38f | ||
|
|
031b226ac6 | ||
|
|
35d535a477 | ||
|
|
8930dfa827 | ||
|
|
c67c9bdbfb | ||
|
|
52d7c549c3 | ||
|
|
9cb4e49849 | ||
|
|
f59db07ef9 | ||
|
|
46bcf1fa89 | ||
|
|
7d15e8466a | ||
|
|
ce2d598d96 | ||
|
|
627b4c1672 | ||
|
|
ee02505468 | ||
|
|
7c8fbb4813 | ||
|
|
8a3f8c6bb9 | ||
|
|
7e938845f0 | ||
|
|
0d6c47d545 | ||
|
|
2f5af2ceb5 | ||
|
|
448bcc90fe | ||
|
|
2fed527c00 | ||
|
|
8fc6b70aa5 | ||
|
|
cf0e1e44ba | ||
|
|
27c997fc14 | ||
|
|
622d9efa24 | ||
|
|
b61e540082 | ||
|
|
d21b575107 | ||
|
|
32776e8242 | ||
|
|
bea6fdcac3 | ||
|
|
e86c474fd5 | ||
|
|
6c36c9ba80 | ||
|
|
e8a2214eee | ||
|
|
91cc79b19f | ||
|
|
df5fb69839 | ||
|
|
9b5fd4514e | ||
|
|
b380956554 | ||
|
|
1de10e927d | ||
|
|
b01519fd54 | ||
|
|
65ef069acc | ||
|
|
c1fcc1f238 | ||
|
|
7983b2a262 | ||
|
|
7ddb1e237d | ||
|
|
5ff536e139 | ||
|
|
bb7d2d9839 | ||
|
|
fc20eb6c7a | ||
|
|
d850228c3c | ||
|
|
f6cef23cd7 | ||
|
|
766c98af22 | ||
|
|
1f0f070e84 | ||
|
|
a90271b282 | ||
|
|
c81680e066 | ||
|
|
0c57f7265a | ||
|
|
f917f247e3 | ||
|
|
aef0f98a68 | ||
|
|
6e5c250af0 | ||
|
|
f37f235d78 | ||
|
|
b973e42174 | ||
|
|
ce6fb3dd6c | ||
|
|
c3262987be | ||
|
|
52ee7b3692 | ||
|
|
04ee825d6b | ||
|
|
0241b57c35 | ||
|
|
24dd855b87 | ||
|
|
4e5fce4adc | ||
|
|
ae94b5118b | ||
|
|
2dd4be802a | ||
|
|
f4fa0512d3 | ||
|
|
d1150cea7a | ||
|
|
4afccc1842 | ||
|
|
4e161a15ef | ||
|
|
76175250cd | ||
|
|
2e58e319e7 | ||
|
|
283e4b2805 | ||
|
|
db63e66333 | ||
|
|
8f2482ac06 | ||
|
|
c99099ea1c | ||
|
|
4b3521a3e3 | ||
|
|
f664af1933 | ||
|
|
03ad294db0 | ||
|
|
a2ec77fc7f | ||
|
|
602bca62a2 | ||
|
|
423afc6d03 | ||
|
|
374398b58d | ||
|
|
6c2b8e6fa0 | ||
|
|
ecc612044e | ||
|
|
896aba0d19 | ||
|
|
45740bef8b | ||
|
|
84ab8524b5 | ||
|
|
071e4d2738 | ||
|
|
3c9d1a56a4 |
@@ -0,0 +1,8 @@
|
||||
## Reviewing PRs
|
||||
|
||||
When reviewing PRs, please respond in a friendly and constructive manner.
|
||||
|
||||
- First, thank the contributor for their contribution in an enthusiastic way.
|
||||
- If you have any suggestions for improvement, provide them clearly and concisely.
|
||||
- If you have any questions about the PR, ask them respectfully.
|
||||
- Let the contributor know that we have many PRs to review and that we appreciate their patience while we review their contribution.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
applyTo: "icons/*.json"
|
||||
---
|
||||
# JSON Metadata Descriptor
|
||||
|
||||
The schema for the JSON metadata descriptor can be found in `icon.schema.json`. It defines the required and optional properties for the JSON files that accompany each icon. The JSON metadata descriptor should be placed in the same directory as the SVG file of the icon and should have the same name as the SVG file, but with a `.json` extension. For example, if the SVG file is named `home.svg`, the JSON metadata descriptor should be named `home.json`.
|
||||
|
||||
## Contributors
|
||||
|
||||
The `contributors` property is a required array of GitHub usernames for the people who created or contributed to the icon. It is used to give credit to contributors and to track the icon's history. Add the PR author to the `contributors` array if they are not already listed and they have made a significant contribution to the icon's SVG.
|
||||
|
||||
## Tags
|
||||
|
||||
The `tags` property is an array of strings that describe the icon and can be used for searching.
|
||||
Validate the tags against the `icon.schema.json` to ensure they are correctly formatted and adhere to the defined structure.
|
||||
Provide tag suggestions based on the name of the icon and the use cases provided in the PR description. Use the existing tags in the repository as a reference for consistency and to avoid duplicates. Don't suggest words like: 'icon' and preferably use single words. Tags should always be in lowercase and should not contain spaces. The name of icon should not be included in the tags, as it is already specified.
|
||||
|
||||
# Categories
|
||||
|
||||
The `categories` property is an array of strings that specify the categories to which the icon belongs, such as "devices", "interface", "media", etc. See the `categories` property in the `icon.schema.json` for more details on the allowed values. The categories should be chosen based on the use cases provided in the PR description and the existing categories in the repository. The categories should be relevant to the icon and should help users find the icon when searching for specific types of icons. The name of icon should not be included in the categories, as it is already specified. Suggest categories based on the name of the icon and the use cases provided in the PR description. Use the existing categories in the repository as a reference for consistency and to avoid duplicates. Categories should always be in lowercase and should not contain spaces.
|
||||
@@ -32,8 +32,37 @@ concurrency:
|
||||
group: continuous-integration
|
||||
|
||||
jobs:
|
||||
check-dispatch-gate:
|
||||
if: github.repository == 'lucide-icons/lucide' &&
|
||||
startsWith(github.event.head_commit.message, 'feat(icons)') &&
|
||||
github.event_name != 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
RELEASE_ENVIRONMENT: ${{ steps.environment.outputs.RELEASE_ENVIRONMENT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
|
||||
- name: Check if latest tag was created today
|
||||
id: environment
|
||||
run: |
|
||||
LATEST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1)
|
||||
TAG_DATE=$(git log -1 --format=%cs "$LATEST_TAG")
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
if [ "$TAG_DATE" == "$TODAY" ]; then
|
||||
echo "::warning::A release tag already exists for today ($TODAY): $LATEST_TAG. Manual approval will be required to proceed."
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release Gated" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "RELEASE_ENVIRONMENT=Icon Release" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
create-release:
|
||||
if: github.repository == 'lucide-icons/lucide' && startsWith(github.event.head_commit.message, 'feat(icons)') && github.event_name != 'workflow_dispatch'
|
||||
if: github.repository == 'lucide-icons/lucide'
|
||||
needs: check-dispatch-gate
|
||||
environment: ${{ needs.check-dispatch-gate.outputs.RELEASE_ENVIRONMENT }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
VERSION: ${{ steps.new-version.outputs.NEW_VERSION }}
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
Lucide **does not accept** brand logos, and we do not plan to add them in the future. This is due to a combination of **legal restrictions**, **design consistency concerns**, and **practical maintenance reasons**.
|
||||
|
||||
[Click here to read our official statement about brand logos in Lucide.](./BRAND_LOGOS_STATEMENT.md)
|
||||
[Click here to read our official statement about brand logos in Lucide.](https://github.com/lucide-icons/lucide/blob/main/BRAND_LOGOS_STATEMENT.md)
|
||||
|
||||
You can [search for similar issues.](https://github.com/lucide-icons/lucide/issues?q=is%3Aissue+${VALUE})
|
||||
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
name: Pull request tags suggestions
|
||||
name: Pull request metadata suggestions
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -6,8 +6,8 @@ on:
|
||||
- 'icons/*.json'
|
||||
|
||||
jobs:
|
||||
pull-request-tags-suggestions:
|
||||
name: Pull Request Tags Suggestions
|
||||
pull-request-metadata-suggestions:
|
||||
name: Pull Request Metadata Suggestions
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate comment markup
|
||||
run: node ./scripts/suggestTags.mts
|
||||
run: node ./scripts/suggestMetaData.mts
|
||||
env:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
PULL_REQUEST_NUMBER: ${{ github.event.number }}
|
||||
@@ -20,9 +20,6 @@ permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
if: github.repository == 'lucide-icons/lucide' && contains('["ericfennis", "karsa-mistmere", "jguddas"]', github.actor)
|
||||
@@ -74,7 +71,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --fail-if-no-match --filter ${{ matrix.package }} version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter ${{ matrix.package }} --fail-if-no-match --no-git-tag-version
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter ${{ matrix.package }} build
|
||||
@@ -108,7 +105,7 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Set new version
|
||||
run: pnpm --filter lucide-static version --new-version ${{ needs.prepare.outputs.VERSION }} --no-git-tag-version
|
||||
run: pnpm version ${{ needs.prepare.outputs.VERSION }} --filter lucide-static --no-git-tag-version
|
||||
|
||||
- name: Move Font
|
||||
run: cp -r lucide-font packages/lucide-static/font
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: Weekly Repo Report
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Run this workflow every Monday at 07:00 UTC
|
||||
schedule:
|
||||
- cron: '0 7 * * 1'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
weekly-summary:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Install Copilot CLI
|
||||
run: npm install -g @github/copilot
|
||||
|
||||
- name: Run the prompt
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
run: |
|
||||
copilot -p "${{ vars.REPO_REPORT_PROMPT }} Write the details to summary.md" --allow-tool='shell(git:*)' --allow-tool=write --no-ask-user
|
||||
cat summary.md >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Post report to Discord (private channel)
|
||||
env:
|
||||
DISCORD_WEBHOOK_URL: ${{ secrets.LUCIDE_DISCORD_WEBHOOK }}
|
||||
run: |
|
||||
jq -Rs '{
|
||||
embeds: [{
|
||||
title: "📊 Weekly Repo Report",
|
||||
description: (.[0:4096]),
|
||||
color: 5814783
|
||||
}]
|
||||
}' summary.md > payload.json
|
||||
curl -sS --fail -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @payload.json \
|
||||
"$DISCORD_WEBHOOK_URL"
|
||||
@@ -53,6 +53,7 @@ docs/.vitepress/data/categoriesData.json
|
||||
docs/.vitepress/data/iconDetails
|
||||
docs/.vitepress/data/relatedIcons.json
|
||||
docs/.vitepress/data/brandStopwords.json
|
||||
docs/.vitepress/data/iconPopularity
|
||||
docs/.vercel
|
||||
docs/.nitro
|
||||
docs/public/og/**
|
||||
|
||||
@@ -35,7 +35,7 @@ Lucide is an open-source icon library that provides 1600+ vector (svg) files for
|
||||
| <img src="https://lucide.dev/framework-logos/js.svg" alt="JS logo" width="48"> | **`lucide`** | [](https://www.npmjs.com/package/lucide) |  | [Docs](https://lucide.dev/guide/lucide) · [Source](./packages/lucide) |
|
||||
| <img src="https://lucide.dev/framework-logos/react.svg" alt="React logo" width="48"> | **`lucide-react`** | [](https://www.npmjs.com/package/lucide-react) |  | [Docs](https://lucide.dev/guide/react) · [Source](./packages/lucide-react) |
|
||||
| <img src="https://lucide.dev/framework-logos/vue.svg" alt="Vue logo" width="48"> | **`@lucide/vue`** | [](https://www.npmjs.com/package/@lucide/vue) |  | [Docs](https://lucide.dev/guide/vue) · [Source](./packages/vue) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`@lucide/svelte`** | [](https://www.npmjs.com/package/@lucide/svelte) |  | [Docs](https://lucide.dev/guide/svelte) · [Source](./packages/@lucide/svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/svelte.svg" alt="Svelte logo" width="48"> | **`@lucide/svelte`** | [](https://www.npmjs.com/package/@lucide/svelte) |  | [Docs](https://lucide.dev/guide/svelte) · [Source](./packages/svelte) |
|
||||
| <img src="https://lucide.dev/framework-logos/solid.svg" alt="Solid logo" width="48"> | **`lucide-solid`** | [](https://www.npmjs.com/package/lucide-solid) |  | [Docs](https://lucide.dev/guide/solid) · [Source](./packages/lucide-solid) |
|
||||
| <img src="https://lucide.dev/framework-logos/preact.svg" alt="Preact logo" width="48"> | **`lucide-preact`** | [](https://www.npmjs.com/package/lucide-preact) |  | [Docs](https://lucide.dev/guide/preact) · [Source](./packages/lucide-preact) |
|
||||
| <img src="https://lucide.dev/framework-logos/react-native.svg" alt="React Native logo" width="48"> | **`lucide-react-native`** | [](https://www.npmjs.com/package/lucide-react-native) |  | [Docs](https://lucide.dev/guide/react-native) · [Source](./packages/lucide-react-native) |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "../category.schema.json",
|
||||
"title": "Navigation, Maps, and POIs",
|
||||
"title": "Navigation & Places",
|
||||
"icon": "compass"
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export default defineConfig({
|
||||
},
|
||||
{ text: 'Packages', link: '/packages' },
|
||||
{ text: 'Showcase', link: '/showcase' },
|
||||
{ text: 'Merch', link: 'https://merch.lucide.dev/' },
|
||||
],
|
||||
sidebar,
|
||||
socialLinks: [
|
||||
@@ -109,7 +110,7 @@ export default defineConfig({
|
||||
],
|
||||
footer: {
|
||||
message: 'Released under the ISC License.',
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Contributors`,
|
||||
copyright: `Copyright © ${new Date().getFullYear()} Lucide Icons`,
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/lucide-icons/lucide/edit/main/docs/:path',
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
},
|
||||
{
|
||||
"name": "navigation",
|
||||
"title": "Navigation, Maps, and POIs"
|
||||
"title": "Navigation & Places"
|
||||
},
|
||||
{
|
||||
"name": "notifications",
|
||||
|
||||
@@ -209,5 +209,24 @@
|
||||
],
|
||||
"source": "https://github.com/shx08/strapi-lucide-icons",
|
||||
"documentation": "https://github.com/shx08/strapi-lucide-icons/blob/main/README.md"
|
||||
},
|
||||
{
|
||||
"name": "lucide-lit",
|
||||
"description": "Implementation of Lucide icons as Lit web components.",
|
||||
"icon": "/framework-logos/lit.svg",
|
||||
"shields": [
|
||||
{
|
||||
"alt": "Latest Stable Version",
|
||||
"src": "https://img.shields.io/npm/v/@gyeonghokim/lucide-lit",
|
||||
"href": "https://www.npmjs.com/package/@gyeonghokim/lucide-lit"
|
||||
},
|
||||
{
|
||||
"alt": "Total Downloads",
|
||||
"src": "https://img.shields.io/npm/dw/@gyeonghokim/lucide-lit",
|
||||
"href": "https://www.npmjs.com/package/@gyeonghokim/lucide-lit"
|
||||
}
|
||||
],
|
||||
"source": "https://github.com/GyeongHoKim/lucide-lit",
|
||||
"documentation": "https://github.com/GyeongHoKim/lucide-lit/blob/main/README.md"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -24,13 +24,6 @@ const getHeadConfig = ({
|
||||
defer: '',
|
||||
},
|
||||
],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
src: 'https://media.bitterbrains.com/main.js?from=LUCIDE&type=top',
|
||||
async: 'true',
|
||||
},
|
||||
],
|
||||
[
|
||||
'meta',
|
||||
{
|
||||
|
||||
@@ -72,6 +72,11 @@ export const reactNativeSidebar = [
|
||||
link: '/guide/react-native/advanced/combining-icons',
|
||||
desc: 'Combine multiple icons into one',
|
||||
},
|
||||
{
|
||||
text: 'Optimizations',
|
||||
link: '/guide/react-native/advanced/optimizations',
|
||||
desc: 'Optimizing your app for performance',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -11,6 +11,14 @@ const { isDark } = useData();
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
function limitHexInput(val: string) {
|
||||
if (val.startsWith('#')) {
|
||||
return val.slice(0, 9);
|
||||
}
|
||||
|
||||
return val.slice(0, 8);
|
||||
}
|
||||
|
||||
const value = computed({
|
||||
get: () => {
|
||||
if (props.modelValue == null || props.modelValue === 'currentColor') {
|
||||
@@ -19,7 +27,7 @@ const value = computed({
|
||||
|
||||
return props.modelValue;
|
||||
},
|
||||
set: (val) => emit('update:modelValue', val),
|
||||
set: (val) => emit('update:modelValue', limitHexInput(val)),
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -41,6 +49,7 @@ const value = computed({
|
||||
class="color-input-text"
|
||||
aria-label="Color picker input"
|
||||
v-model="value"
|
||||
maxlength="9"
|
||||
placeholder="[default]"
|
||||
/>
|
||||
</div>
|
||||
@@ -91,6 +100,7 @@ const value = computed({
|
||||
background: transparent;
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 13px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
text-align: left;
|
||||
border-radius: 8px;
|
||||
cursor: text;
|
||||
|
||||
@@ -6,10 +6,10 @@ import SelectIcon from './SelectIcon.vue';
|
||||
|
||||
defineProps<{
|
||||
id?: string;
|
||||
items?: { name: string; route: string; icon: string; iconDark?: string }[];
|
||||
items?: { name: string; value: string; icon?: string; iconDark?: string }[];
|
||||
}>();
|
||||
|
||||
const selected = defineModel<{ name: string; icon: string; iconDark?: string }>();
|
||||
const selected = defineModel<{ name: string; value: string; icon?: string; iconDark?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -19,14 +19,18 @@ const selected = defineModel<{ name: string; icon: string; iconDark?: string }>(
|
||||
class="select-button"
|
||||
:id="id"
|
||||
>
|
||||
<SelectIcon :item="selected" />
|
||||
<slot name="start-icon">
|
||||
<SelectIcon v-if="selected.icon" :item="selected" />
|
||||
</slot>
|
||||
<span class="select-text">{{ selected.name }}</span>
|
||||
<span class="select-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<slot name="end-icon">
|
||||
<Icon
|
||||
:iconNode="chevronsUpDown"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</slot>
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
@@ -43,8 +47,9 @@ const selected = defineModel<{ name: string; icon: string; iconDark?: string }>(
|
||||
:value="item"
|
||||
as="template"
|
||||
>
|
||||
|
||||
<li :class="['select-option', { active, selected }]">
|
||||
<SelectIcon :item="item" />
|
||||
<SelectIcon v-if="item.icon" :item="item" />
|
||||
<span :class="['option-text', { selected }]">{{ item.name }}</span>
|
||||
<span
|
||||
v-if="selected"
|
||||
|
||||
@@ -2,25 +2,28 @@
|
||||
const { item } = defineProps<{
|
||||
item: {
|
||||
name: string;
|
||||
icon: string;
|
||||
icon?: string;
|
||||
iconDark?: string;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
<div v-if="item.icon || item.iconDark">
|
||||
<img
|
||||
v-if="item.icon"
|
||||
:src="item.icon"
|
||||
:class="{ 'select-item-icon': true, light: item.iconDark }"
|
||||
:alt="`${item.name} logo`"
|
||||
loading="lazy"
|
||||
/>
|
||||
<img
|
||||
v-if="item.iconDark"
|
||||
:src="item.iconDark"
|
||||
:alt="`${item.name} logo`"
|
||||
class="select-item-icon dark"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
{
|
||||
ariaLabel: 'Get JavaScript certificates from certificates.dev',
|
||||
image: '/certificates-dev/js-certificates-dev.jpg',
|
||||
href: 'https://certificates.dev/javascript?friend=LUCIDE',
|
||||
routes: [
|
||||
'/guide/lucide/',
|
||||
'/guide/svelte/',
|
||||
'/guide/solid/',
|
||||
'/guide/astro/',
|
||||
'/guide/static/'
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get React certificates from certificates.dev',
|
||||
image: '/certificates-dev/react-certificates-dev.jpg',
|
||||
href: 'https://certificates.dev/react?friend=LUCIDE',
|
||||
routes: [
|
||||
'/guide/react/',
|
||||
'/guide/react-native/',
|
||||
'/guide/preact/',
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get Vue certificates from certificates.dev',
|
||||
href: 'https://certificates.dev/vuejs?friend=LUCIDE',
|
||||
image: '/certificates-dev/vue-certificates-dev.jpg',
|
||||
routes: [
|
||||
'/guide/vue/',
|
||||
],
|
||||
},
|
||||
{
|
||||
ariaLabel: 'Get Angular certificates from certificates.dev',
|
||||
href: 'https://certificates.dev/angular?friend=LUCIDE',
|
||||
image: '/certificates-dev/angular-certificates-dev.jpg',
|
||||
routes: [
|
||||
'/guide/angular/',
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ routes }) => {
|
||||
return routes.some(route => router.route.path.startsWith(route));
|
||||
});
|
||||
|
||||
return current || frameworks[0];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a class="certificates-link" :href="selected.href" :aria-label="selected.ariaLabel" target="_blank" rel="noopener noreferrer">
|
||||
<img :src="selected.image" :alt="selected.ariaLabel" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.certificates-link {
|
||||
/* padding: 20px; */
|
||||
border-radius: 12px;
|
||||
min-height: 100px;
|
||||
text-align: center;
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background-color: var(--vp-carbon-ads-bg-color);
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.certificates-link img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: 6px;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.certificates-link:hover img {
|
||||
transform: scale(1.05);
|
||||
opacity: 0.9;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -11,49 +11,49 @@ const { page } = useData();
|
||||
const router = useRouter();
|
||||
|
||||
const frameworks = [
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', route: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', route: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', route: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', route: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', route: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', route: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', route: '/guide/preact/' },
|
||||
{ name: 'Vanilla', icon: '/framework-logos/js.svg', value: '/guide/lucide/' },
|
||||
{ name: 'React', icon: '/framework-logos/react.svg', value: '/guide/react/' },
|
||||
{ name: 'Vue', icon: '/framework-logos/vue.svg', value: '/guide/vue/' },
|
||||
{ name: 'Svelte', icon: '/framework-logos/svelte.svg', value: '/guide/svelte/' },
|
||||
{ name: 'Solid', icon: '/framework-logos/solid.svg', value: '/guide/solid/' },
|
||||
{ name: 'Angular', icon: '/framework-logos/angular.svg', value: '/guide/angular/' },
|
||||
{ name: 'Preact', icon: '/framework-logos/preact.svg', value: '/guide/preact/' },
|
||||
{
|
||||
name: 'React Native',
|
||||
icon: '/framework-logos/react-native.svg',
|
||||
route: '/guide/react-native/',
|
||||
value: '/guide/react-native/',
|
||||
},
|
||||
{
|
||||
name: 'Astro',
|
||||
icon: '/framework-logos/astro.svg',
|
||||
iconDark: '/framework-logos/astro-dark.svg',
|
||||
route: '/guide/astro/',
|
||||
value: '/guide/astro/',
|
||||
},
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', route: '/guide/static/' },
|
||||
{ name: 'Static', icon: '/framework-logos/svg.svg', value: '/guide/static/' },
|
||||
];
|
||||
|
||||
const fallbackFramework = useLocalStorage('lucide-docs-fallback-framework', frameworks[1]);
|
||||
|
||||
const selected = computed(() => {
|
||||
const current = frameworks.find(({ route }) => {
|
||||
const current = frameworks.find(({ value }) => {
|
||||
if (router.route.path?.startsWith?.('/guide')) {
|
||||
const [, , framework] = router.route.path.split('/');
|
||||
const [, frameWorkRoute] = route.split('/').filter(Boolean);
|
||||
const [, frameWorkRoute] = value.split('/').filter(Boolean);
|
||||
|
||||
return framework === frameWorkRoute;
|
||||
}
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === route;
|
||||
return router.route.path.split('/').slice(0, 3).join('/') === value;
|
||||
});
|
||||
|
||||
return current || fallbackFramework.value;
|
||||
return current || (fallbackFramework.value?.value ? fallbackFramework.value : frameworks[0]);
|
||||
});
|
||||
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; route: string }) {
|
||||
function onSelectFramework(item: { name: string; icon: string; iconDark?: string; value: string }) {
|
||||
fallbackFramework.value = item;
|
||||
if (item.route !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.route, item.route);
|
||||
if (item.value !== router.route.path) {
|
||||
const likeRoute = router.route.path.replace(selected.value.value, item.value);
|
||||
|
||||
const hasRoute = sidebar[item.route]?.some((section) =>
|
||||
const hasRoute = sidebar[item.value]?.some((section) =>
|
||||
section?.items?.some(({ link }) => link === likeRoute),
|
||||
);
|
||||
|
||||
@@ -62,7 +62,7 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
return;
|
||||
}
|
||||
|
||||
router.go(item.route);
|
||||
router.go(item.value);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -85,12 +85,12 @@ function onSelectFramework(item: { name: string; icon: string; iconDark?: string
|
||||
/>
|
||||
</div>
|
||||
<VPSidebarGroup
|
||||
:key="selected.route"
|
||||
:key="selected.value"
|
||||
v-if="
|
||||
page?.relativePath?.startsWith?.('guide') &&
|
||||
!page?.relativePath?.startsWith?.(selected.route.substring(1))
|
||||
!page?.relativePath?.startsWith?.(selected.value.substring(1))
|
||||
"
|
||||
:items="sidebar[selected.route]"
|
||||
:items="sidebar[selected.value]"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -13,23 +13,21 @@ const { go } = useRouter()
|
||||
<HomeSectionTitle>Available For:</HomeSectionTitle>
|
||||
<div class="packages-list">
|
||||
<a
|
||||
v-for="{ name, logo, logoDark } in data.packages"
|
||||
:href="`/guide/packages/${name}`"
|
||||
v-for="{ name, label, path, logo, logoDark } in data.packages"
|
||||
:href="path"
|
||||
class="package-logo"
|
||||
:aria-label="`Read more about: ${name} package`"
|
||||
@click.prevent="go(`/guide/packages/${name}`)"
|
||||
@click.prevent="go(path)"
|
||||
>
|
||||
<img
|
||||
:src="logo"
|
||||
:class="{ light: logoDark, 'image-logo': true }"
|
||||
:alt="`${name} logo`"
|
||||
:alt="label"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="logoDark"
|
||||
:src="logoDark"
|
||||
:alt="`${name} logo`"
|
||||
:alt="label"
|
||||
class="image-logo dark"
|
||||
loading="lazy"
|
||||
/>
|
||||
|
||||
@@ -188,7 +188,7 @@ const deprecatedTitle = computed(() => {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
scrollbar-width: thin; /* can also be normal, or none, to not render scrollbar */
|
||||
scrollbar-color: currentColor transparent; /* foreground background */
|
||||
scrollbar-color: var(--vp-c-text-4) transparent; /* foreground background */
|
||||
}
|
||||
.horizontal-scroller::-webkit-scrollbar {
|
||||
width: 0;
|
||||
|
||||
@@ -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'];
|
||||
@@ -19,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>
|
||||
@@ -57,6 +83,16 @@ const iconComponent = computed(() => {
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
<a
|
||||
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}`"
|
||||
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>
|
||||
|
||||
@@ -75,6 +111,8 @@ const iconComponent = computed(() => {
|
||||
position: relative;
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-container > :deep(svg:not(.icon-grid)) {
|
||||
width: 100%;
|
||||
@@ -85,6 +123,34 @@ const iconComponent = computed(() => {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.studio-button {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
right: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--vp-c-text-2);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s, color 0.2s, background-color 0.2s;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.icon-container:hover .studio-button,
|
||||
.studio-button:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.studio-button:hover,
|
||||
.studio-button:focus-visible {
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.icon-component.customizable {
|
||||
will-change: width, height, stroke-width, stroke;
|
||||
/* color: var(--customize-color, currentColor);
|
||||
|
||||
@@ -44,8 +44,12 @@ function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categoriesMap } = useFetchCategories();
|
||||
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
|
||||
const {
|
||||
execute: fetchCategories,
|
||||
data: categoriesMap,
|
||||
isFetching: isFetchingCategories,
|
||||
} = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
@@ -117,6 +121,9 @@ const categoriesList = computed(() => {
|
||||
}, []);
|
||||
});
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && (tags.value == null || categoriesMap.value == null),
|
||||
);
|
||||
|
||||
const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(categoriesList, {
|
||||
itemHeight: ICON_SIZE + ICON_GRID_GAP,
|
||||
@@ -135,15 +142,21 @@ onMounted(() => {
|
||||
useEventListener(window, 'scroll', containerProps.onScroll);
|
||||
});
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
function loadSearchMetadata() {
|
||||
if (tags.value == null && !isFetchingTags.value) {
|
||||
void fetchTags();
|
||||
}
|
||||
if (categoriesMap.value == null) {
|
||||
fetchCategories();
|
||||
if (categoriesMap.value == null && !isFetchingCategories.value) {
|
||||
void fetchCategories();
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchQuery, (searchString) => {
|
||||
if (searchString !== '') {
|
||||
loadSearchMetadata();
|
||||
}
|
||||
});
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
|
||||
@@ -156,11 +169,11 @@ function handleCloseDrawer() {
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
|
||||
if (selectedCategory.value) {
|
||||
url.hash = selectedCategory.value;
|
||||
}
|
||||
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
</script>
|
||||
@@ -169,6 +182,7 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar class="category-search">
|
||||
<InputSearch
|
||||
@@ -177,11 +191,11 @@ function handleCloseDrawer() {
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="input-wrapper"
|
||||
ref="searchInput"
|
||||
@focus="onFocusSearchInput"
|
||||
@focus="loadSearchMetadata"
|
||||
/>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { IconEntity } from '../../types';
|
||||
import { useElementSize, useEventListener, useVirtualList } from '@vueuse/core';
|
||||
import { useRoute } from 'vitepress';
|
||||
import IconGrid from './IconGrid.vue';
|
||||
import Select from '../base/Select.vue';
|
||||
import InputSearch from '../base/InputSearch.vue';
|
||||
import useSearch from '../../composables/useSearch';
|
||||
import useSearchInput from '../../composables/useSearchInput';
|
||||
@@ -14,9 +15,26 @@ import useFetchCategories from '../../composables/useFetchCategories';
|
||||
import chunkArray from '../../utils/chunkArray';
|
||||
import CarbonAdOverlay from './CarbonAdOverlay.vue';
|
||||
import useSearchPlaceholder from '../../utils/useSearchPlaceholder.ts';
|
||||
import Icon from '@lucide/vue/src/Icon';
|
||||
import { listSortDescending } from '~/.vitepress/data/iconNodes';
|
||||
|
||||
const ICON_SIZE = 56;
|
||||
const ICON_GRID_GAP = 8;
|
||||
const SORTING = [
|
||||
{
|
||||
name: 'Popularity',
|
||||
value: 'popularity',
|
||||
},
|
||||
{
|
||||
name: 'Release date',
|
||||
value: 'release-date',
|
||||
},
|
||||
{
|
||||
name: 'Name',
|
||||
value: 'name',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
const initialGridItems = computed(() => {
|
||||
if (containerWidth.value === 0) return 120;
|
||||
@@ -32,9 +50,14 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const activeIconName = ref(null);
|
||||
const selectedSort = ref(SORTING[0])
|
||||
|
||||
const { execute: fetchTags, data: tags } = useFetchTags();
|
||||
const { execute: fetchCategories, data: categories } = useFetchCategories();
|
||||
const { execute: fetchTags, data: tags, isFetching: isFetchingTags } = useFetchTags();
|
||||
const {
|
||||
execute: fetchCategories,
|
||||
data: categories,
|
||||
isFetching: isFetchingCategories,
|
||||
} = useFetchCategories();
|
||||
|
||||
const overviewEl = ref<HTMLElement | null>(null);
|
||||
const { width: containerWidth } = useElementSize(overviewEl);
|
||||
@@ -43,12 +66,29 @@ const columnSize = computed(() => {
|
||||
return Math.floor(containerWidth.value / (ICON_SIZE + ICON_GRID_GAP));
|
||||
});
|
||||
|
||||
const sortedIcons = computed(() => {
|
||||
switch (selectedSort.value.value) {
|
||||
case 'popularity':
|
||||
return [...props.icons].sort((a, b) => (b.popularity || 0) - (a.popularity || 0));
|
||||
case 'release-date':
|
||||
return [...props.icons].sort((a, b) => {
|
||||
const aDate = a.createdRelease?.date ? new Date(a.createdRelease.date).getTime() : 0;
|
||||
const bDate = b.createdRelease?.date ? new Date(b.createdRelease.date).getTime() : 0;
|
||||
return bDate - aDate;
|
||||
});
|
||||
case 'name':
|
||||
return [...props.icons].sort((a, b) => a.name.localeCompare(b.name));
|
||||
default:
|
||||
return props.icons;
|
||||
}
|
||||
});
|
||||
|
||||
const mappedIcons = computed(() => {
|
||||
if (tags.value == null) {
|
||||
return props.icons;
|
||||
return sortedIcons.value;
|
||||
}
|
||||
|
||||
return props.icons.map((icon) => {
|
||||
return sortedIcons.value.map((icon) => {
|
||||
const iconTags = tags.value[icon.name];
|
||||
const iconCategories = categories.value?.[icon.name] ?? [];
|
||||
|
||||
@@ -68,11 +108,15 @@ const { shortcutText: kbdSearchShortcut } = useSearchShortcut(() => {
|
||||
|
||||
const searchResults = useSearch(searchQueryDebounced, mappedIcons, [
|
||||
{ name: 'name', weight: 3 },
|
||||
{ name: 'aliases', weight: 3 },
|
||||
{ name: 'aliases', weight: 8 },
|
||||
{ name: 'tags', weight: 2 },
|
||||
{ name: 'categories', weight: 1 },
|
||||
]);
|
||||
|
||||
const searchPlaceholder = useSearchPlaceholder(searchQuery, searchResults);
|
||||
const isSearchMetadataLoading = computed(
|
||||
() => searchQuery.value.length > 0 && (tags.value == null || categories.value == null),
|
||||
);
|
||||
|
||||
const chunkedIcons = computed(() => {
|
||||
return chunkArray(searchResults.value, columnSize.value);
|
||||
@@ -98,15 +142,21 @@ function setActiveIconName(name: string) {
|
||||
activeIconName.value = name;
|
||||
}
|
||||
|
||||
function onFocusSearchInput() {
|
||||
if (tags.value == null) {
|
||||
fetchTags();
|
||||
function loadSearchMetadata() {
|
||||
if (tags.value == null && !isFetchingTags.value) {
|
||||
void fetchTags();
|
||||
}
|
||||
if (categories.value == null) {
|
||||
fetchCategories();
|
||||
if (categories.value == null && !isFetchingCategories.value) {
|
||||
void fetchCategories();
|
||||
}
|
||||
}
|
||||
|
||||
watch(searchQuery, (searchString) => {
|
||||
if (searchString !== '') {
|
||||
loadSearchMetadata();
|
||||
}
|
||||
});
|
||||
|
||||
const NoResults = defineAsyncComponent(() => import('./NoResults.vue'));
|
||||
|
||||
const IconDetailOverlay = defineAsyncComponent(() => import('./IconDetailOverlay.vue'));
|
||||
@@ -124,7 +174,7 @@ function handleCloseDrawer() {
|
||||
if (searchQueryDebounced.value) {
|
||||
url.searchParams.set('search', searchQueryDebounced.value);
|
||||
}
|
||||
|
||||
|
||||
window.history.pushState({}, '', url);
|
||||
}
|
||||
</script>
|
||||
@@ -133,6 +183,7 @@ function handleCloseDrawer() {
|
||||
<div
|
||||
ref="overviewEl"
|
||||
class="overview-container"
|
||||
:class="{ 'icon-drawer-open': activeIconName }"
|
||||
>
|
||||
<StickyBar>
|
||||
<InputSearch
|
||||
@@ -141,11 +192,25 @@ function handleCloseDrawer() {
|
||||
ref="searchInput"
|
||||
:shortcut="kbdSearchShortcut"
|
||||
class="input-wrapper"
|
||||
@focus="onFocusSearchInput"
|
||||
@focus="loadSearchMetadata"
|
||||
/>
|
||||
|
||||
<Select
|
||||
id="sort-select"
|
||||
:items="SORTING"
|
||||
v-model="selectedSort"
|
||||
>
|
||||
<template #start-icon>
|
||||
<Icon
|
||||
:iconNode="listSortDescending"
|
||||
class="chevron-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</template>
|
||||
</Select>
|
||||
</StickyBar>
|
||||
<NoResults
|
||||
v-if="searchPlaceholder.isNoResults"
|
||||
v-if="searchPlaceholder.isNoResults && !isSearchMetadataLoading"
|
||||
:searchQuery="searchPlaceholder.query"
|
||||
:isBrandSearch="searchPlaceholder.isBrand"
|
||||
@clear="searchQuery = ''"
|
||||
|
||||
@@ -73,13 +73,11 @@ const customizingActive = computed(() => {
|
||||
id="icon-color"
|
||||
label="Color"
|
||||
>
|
||||
<template #display>
|
||||
<ColorPicker
|
||||
v-model="color"
|
||||
id="icon-color"
|
||||
class="color-picker"
|
||||
/>
|
||||
</template>
|
||||
<ColorPicker
|
||||
v-model="color"
|
||||
id="icon-color"
|
||||
class="color-picker"
|
||||
/>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
@@ -161,6 +159,6 @@ const customizingActive = computed(() => {
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
margin-left: auto;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -15,6 +15,50 @@
|
||||
margin-bottom: 32px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: 0 16px 24px var(--vp-c-bg);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 14px;
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
color: var(--vp-c-text-2);
|
||||
flex-shrink: 0;
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button):hover,
|
||||
.search-bar :deep(.select-button):focus {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-options) {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-option) {
|
||||
padding-right: 36px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.search-bar :deep(.select-button) {
|
||||
padding: 11px 16px 11px 16px;
|
||||
}
|
||||
|
||||
.search-bar :deep(.select-button) .select-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
|
||||
@@ -42,7 +42,6 @@ const links = computed(() => [
|
||||
class="VPFooter"
|
||||
:class="{ 'has-sidebar': hasSidebar }"
|
||||
>
|
||||
<SurveyAskOverlay/>
|
||||
<div class="container">
|
||||
<div class="sponsors">
|
||||
<a
|
||||
|
||||
@@ -20,16 +20,6 @@ const useSearch = <T>(
|
||||
return index.value.search(query.value).map((result) => result.item);
|
||||
}
|
||||
|
||||
if (keys.length !== 0) {
|
||||
const mainKey = keys[0].name;
|
||||
|
||||
return collection.value.sort((a, b) => {
|
||||
const aString = a[mainKey as keyof T] as string;
|
||||
const bString = b[mainKey as keyof T] as string;
|
||||
return aString.localeCompare(bString);
|
||||
});
|
||||
}
|
||||
|
||||
return collection.value;
|
||||
});
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCat
|
||||
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
|
||||
import FrameworkSelect from './components/guide/FrameworkSelect.vue';
|
||||
import SnackPlayer from './components/editors/SnackPlayer.vue';
|
||||
import CertificatesLink from './components/guide/CertificatesLink.vue';
|
||||
|
||||
const theme: Partial<Theme> = {
|
||||
extends: DefaultTheme,
|
||||
@@ -23,7 +22,6 @@ const theme: Partial<Theme> = {
|
||||
'sidebar-nav-after': () => h(IconsSidebarNavAfter),
|
||||
'home-hero-image': () => h(HomeHeroIconsCard),
|
||||
'home-hero-actions-after': () => h(HomeHeroAfter),
|
||||
'aside-ads-before': () => h(CertificatesLink),
|
||||
// Keep this here for now, we might want to add more things to the top bar in the future
|
||||
// 'layout-top': () => h(LayoutTop),
|
||||
});
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
animation: .3s transition 0s ease;
|
||||
} */
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.VPHome {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -117,6 +121,10 @@
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.overview-container.icon-drawer-open {
|
||||
padding-bottom: 288px;
|
||||
}
|
||||
|
||||
.VPHomeHero .container {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
@@ -144,14 +152,6 @@
|
||||
.VPHomeHero .container {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
html.has-bb-banner {
|
||||
--vp-layout-top-height: 72px;
|
||||
}
|
||||
|
||||
html.has-bb-banner {
|
||||
--Dark-Background: #161618;
|
||||
}
|
||||
}
|
||||
|
||||
.VPNavBarHamburger .container > span {
|
||||
@@ -162,3 +162,24 @@
|
||||
margin: 0 auto 48px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
/* 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 */
|
||||
}
|
||||
.VPSidebar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.VPSidebar::-webkit-scrollbar-thumb {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface IconEntity extends IconMetaData {
|
||||
externalLibrary?: ExternalLibs;
|
||||
createdRelease?: Release;
|
||||
changedRelease?: Release;
|
||||
popularity?: number;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PageData } from 'vitepress';
|
||||
import { type PageData } from 'vitepress';
|
||||
import getStructuredData from './getStructuredData';
|
||||
import { createGeneralOGImage, createIconOGImage } from './createOGImage';
|
||||
|
||||
|
||||
+64
-28
@@ -12,19 +12,23 @@ Implementation of the Lucide icon library for web applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide@next
|
||||
pnpm add lucide
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide@next
|
||||
yarn add lucide
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide@next
|
||||
npm install lucide
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide@next
|
||||
bun add lucide
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add lucide
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -38,19 +42,23 @@ Implementation of the Lucide icon library for React applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-react@next
|
||||
pnpm add lucide-react
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-react@next
|
||||
yarn add lucide-react
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-react@next
|
||||
npm install lucide-react
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-react@next
|
||||
bun add lucide-react
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add lucide-react
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -80,6 +88,10 @@ npm install @lucide/vue
|
||||
bun add @lucide/vue
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add @lucide/vue
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
For more details, see the [documentation](./vue/index.md).
|
||||
@@ -91,19 +103,23 @@ Implementation of the Lucide icon library for Svelte applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add @lucide/svelte@next
|
||||
pnpm add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add @lucide/svelte@next
|
||||
yarn add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install @lucide/svelte@next
|
||||
npm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add @lucide/svelte@next
|
||||
bun add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add @lucide/svelte
|
||||
```
|
||||
:::
|
||||
> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package.
|
||||
@@ -117,19 +133,23 @@ Implementation of the Lucide icon library for Solid applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-solid@next
|
||||
pnpm add lucide-solid
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-solid@next
|
||||
yarn add lucide-solid
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-solid@next
|
||||
npm install lucide-solid
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-solid@next
|
||||
bun add lucide-solid
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add lucide-solid
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -158,6 +178,10 @@ npm install @lucide/angular
|
||||
bun add @lucide/angular
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add @lucide/angular
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
For more details, see the [documentation](./angular/index.md).
|
||||
@@ -169,19 +193,23 @@ Implementation of the Lucide icon library for preact applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-preact@next
|
||||
pnpm add lucide-preact
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-preact@next
|
||||
yarn add lucide-preact
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-preact@next
|
||||
npm install lucide-preact
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-preact@next
|
||||
bun add lucide-preact
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add lucide-preact
|
||||
```
|
||||
|
||||
|
||||
@@ -196,19 +224,23 @@ Implementation of the Lucide icon library for Astro applications.
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add @lucide/astro@next
|
||||
pnpm add @lucide/astro
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add @lucide/astro@next
|
||||
yarn add @lucide/astro
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install @lucide/astro@next
|
||||
npm install @lucide/astro
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add @lucide/astro@next
|
||||
bun add @lucide/astro
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add @lucide/astro
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -222,19 +254,23 @@ Implementation of the Lucide icon library for multiple usages that like to use:
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm add lucide-static@next
|
||||
pnpm add lucide-static
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add lucide-static@next
|
||||
yarn add lucide-static
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install lucide-static@next
|
||||
npm install lucide-static
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add lucide-static@next
|
||||
bun add lucide-static
|
||||
```
|
||||
|
||||
```sh [deno]
|
||||
deno add lucide-static
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Optimizations - React Native
|
||||
description: Learn how to optimize the bundle size of your React Native and Expo web exports by tree-shaking Lucide icons.
|
||||
---
|
||||
|
||||
# Optimizations
|
||||
|
||||
By default you import icons from the `lucide-react-native` entry point:
|
||||
|
||||
```jsx
|
||||
import { Camera } from 'lucide-react-native';
|
||||
```
|
||||
|
||||
This is convenient, but it relies on the bundler to _tree-shake_ the entry point (a "barrel" file that re-exports every icon) so that only the icons you actually use end up in your bundle.
|
||||
|
||||
On native platforms this is usually fine. On **web exports** it can be a problem, because the Metro bundler is currently not very good at tree-shaking barrel imports and may include **all** icons in your final bundle.
|
||||
|
||||
## The Metro tree-shaking limitation
|
||||
|
||||
When you build for web with Expo (`expo export --platform web`), Metro is used to bundle your JavaScript. Metro bundles modules on-demand and, by default, does not remove unused exports from a barrel file.
|
||||
|
||||
This means an import like the one below can pull in the entire icon set, even though only a single icon is rendered:
|
||||
|
||||
```jsx
|
||||
// May bundle every Lucide icon on web exports
|
||||
import { Camera } from 'lucide-react-native';
|
||||
```
|
||||
|
||||
Expo documents an experimental optimization for [React Native web imports](https://docs.expo.dev/guides/tree-shaking/#react-native-web-imports) and for [removing unused imports and exports](https://docs.expo.dev/guides/tree-shaking/#remove-unused-imports-and-exports). In practice this tree-shaking does **not** currently work reliably for `lucide-react-native` barrel imports, so the unused icons are not removed from web exports.
|
||||
|
||||
## Recommended: import icons individually
|
||||
|
||||
To guarantee that only the icons you use are bundled — regardless of the bundler's tree-shaking capabilities — import each icon directly from its own module:
|
||||
|
||||
```jsx
|
||||
import Camera from 'lucide-react-native/icons/camera';
|
||||
|
||||
// Usage
|
||||
const App = () => {
|
||||
return <Camera />;
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
Because each icon lives in its own file, the bundler only ever includes the icons you explicitly import. This keeps your web export small without depending on Metro's experimental tree-shaking.
|
||||
|
||||
The icon module name is the [kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case) version of the icon name. For example, the `ArrowRight` icon is imported from `lucide-react-native/icons/arrow-right`:
|
||||
|
||||
```jsx
|
||||
import ArrowRight from 'lucide-react-native/icons/arrow-right';
|
||||
```
|
||||
|
||||
::: tip
|
||||
Individual icon imports work identically on native and web, so you can safely use this style across your whole project to keep bundles as small as possible on every platform.
|
||||
:::
|
||||
@@ -15,24 +15,22 @@ Make sure you have a Svelte environment set up. If you don't have one yet, you c
|
||||
|
||||
## Installation
|
||||
|
||||
<!-- TODO: v1 Remove After full release -->
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [pnpm]
|
||||
pnpm install @lucide/svelte@next
|
||||
pnpm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [yarn]
|
||||
yarn add @lucide/svelte@next
|
||||
yarn add @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [npm]
|
||||
npm install @lucide/svelte@next
|
||||
npm install @lucide/svelte
|
||||
```
|
||||
|
||||
```sh [bun]
|
||||
bun add @lucide/svelte@next
|
||||
bun add @lucide/svelte
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
import iconNodes from '../.vitepress/data/iconNodes';
|
||||
import * as iconDetails from '../.vitepress/data/iconDetails';
|
||||
|
||||
export default {
|
||||
async load() {
|
||||
return {
|
||||
icons: Object.entries(iconNodes).map(([name, iconNode]) => ({ name, iconNode })),
|
||||
icons: Object.entries(iconDetails).map(
|
||||
([, { name, iconNode, popularity, createdRelease, aliases = [] }]) => ({
|
||||
name,
|
||||
iconNode,
|
||||
popularity: popularity?.count ?? 0,
|
||||
aliases: aliases
|
||||
.map((alias) => (typeof alias === 'string' ? alias : alias?.name))
|
||||
.filter((alias): alias is string => Boolean(alias)),
|
||||
createdRelease,
|
||||
}),
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
+26
-24
@@ -9,14 +9,15 @@
|
||||
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
|
||||
"docs:preview": "vitepress preview",
|
||||
"build:docs": "vitepress build",
|
||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mjs",
|
||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mjs",
|
||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mjs",
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mjs",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mjs",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mjs",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mjs",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mjs",
|
||||
"prebuild:iconNodes": "node ./scripts/writeIconNodes.mts",
|
||||
"prebuild:metaJson": "node ./scripts/writeIconMetaIndex.mts",
|
||||
"prebuild:releaseJson": "node ./scripts/writeReleaseMetadata.mts",
|
||||
"prebuild:categoriesJson": "node ./scripts/writeCategoriesMetadata.mts",
|
||||
"prebuild:relatedIcons": "node ./scripts/writeIconRelatedIcons.mts",
|
||||
"prebuild:iconDetails": "node ./scripts/writeIconDetails.mts",
|
||||
"prebuild:iconPopularity": "node ./scripts/writeiconPopularity.mts",
|
||||
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
|
||||
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
|
||||
"dev": "npx nitropack dev",
|
||||
"prebuild:api": "npx nitropack prepare",
|
||||
"build:api": "npx nitropack build",
|
||||
@@ -31,31 +32,32 @@
|
||||
"@lucide/shared": "workspace:*",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@types/semver": "^7.7.1",
|
||||
"h3": "^1.15.9",
|
||||
"h3": "^1.15.11",
|
||||
"markdown-it": "^14.3.0",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"nitropack": "^2.13.4",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"svg-path-commander": "^2.1.11",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-group-icons": "^1.6.5",
|
||||
"vitepress-plugin-llms": "^1.11.1",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"semver": "^7.7.3",
|
||||
"semver": "^7.8.5",
|
||||
"sharp": "^0.34.5",
|
||||
"shiki": "^3.15.0",
|
||||
"shiki": "^3.23.0",
|
||||
"simple-git": "^3.36.0",
|
||||
"sitemap": "^7.1.2",
|
||||
"sitemap": "^7.1.3",
|
||||
"svg-path-commander": "^2.2.1",
|
||||
"svg-pathdata": "^6.0.3",
|
||||
"svgson": "^5.3.1"
|
||||
"svgson": "^5.3.1",
|
||||
"vitepress": "^1.6.4",
|
||||
"vitepress-plugin-group-icons": "^1.7.5",
|
||||
"vitepress-plugin-llms": "^1.13.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@floating-ui/vue": "^1.1.9",
|
||||
"@floating-ui/vue": "^1.1.11",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@lucide/vue": "workspace:*",
|
||||
"@vueuse/components": "^14.0.0",
|
||||
"@vueuse/core": "^14.0.0",
|
||||
"@vueuse/components": "^14.3.0",
|
||||
"@vueuse/core": "^14.3.0",
|
||||
"@vueuse/integrations": "^14.3.0",
|
||||
"element-to-path": "^1.2.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"fuse.js": "^7.5.0",
|
||||
"jszip": "^3.10.1",
|
||||
"lodash-es": "^4.18.1",
|
||||
"lucide": "workspace:*",
|
||||
@@ -64,6 +66,6 @@
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"sandpack-vue3": "3.1.12",
|
||||
"vue": "^3.5.24"
|
||||
"vue": "^3.5.39"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 83 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 200" fill="none">
|
||||
<path fill="#00e8ff" d="M40 120l20-60l90 90l-30 50l-40-40h-20" />
|
||||
<path fill="#283198" d="M80 160 L80 80 L120 40 L 120 120 M0 160 L40 200 L40 120 L20 120" />
|
||||
<path fill="#324fff" d="M40 120v-80l40-40v80M120 200v-80l40-40v80M0 160v-80l40 40" />
|
||||
<path fill="#0ff" d="M40 200v-80l40 40" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
@@ -26,6 +26,7 @@ const writeIconFiles = icons.map(async (iconFileName) => {
|
||||
import iconNode from '../iconNodes/${iconName}.node.json'
|
||||
import metaData from '../../../../icons/${iconName}.json'
|
||||
import releaseData from '../releaseMetadata/${iconName}.json'
|
||||
import popularity from '../iconPopularity/${iconName}.json'
|
||||
|
||||
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion } = metaData
|
||||
|
||||
@@ -37,6 +38,7 @@ const iconDetails = {
|
||||
categories,
|
||||
aliases,
|
||||
deprecated,
|
||||
popularity,
|
||||
deprecationReason,
|
||||
toBeRemovedInVersion,
|
||||
...releaseData,
|
||||
@@ -42,7 +42,7 @@ const getRelatedIcons = (currentIcon, icons) => {
|
||||
};
|
||||
|
||||
const iconsMetaDataPromises = svgFiles.map(async (iconName) => {
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`);
|
||||
const metaDataFileContent = await fs.promises.readFile(`../icons/${iconName}`, 'utf-8');
|
||||
const metaData = JSON.parse(metaDataFileContent);
|
||||
|
||||
const name = iconName.replace('.json', '');
|
||||
@@ -70,10 +70,10 @@ const comparisonsPromises = tags.map(async (tag, index) => {
|
||||
};
|
||||
});
|
||||
|
||||
const comparisons = await Promise.all(comparisonsPromises);
|
||||
const comparisons = (await Promise.all(comparisonsPromises)).filter((comparison) => comparison != null && Object.keys(comparison.iconFiles).length > 0);
|
||||
const newReleaseMetaData = {};
|
||||
|
||||
comparisons.forEach(({ tag, iconFiles, date } = {}) => {
|
||||
comparisons.forEach(({ tag, iconFiles, date }) => {
|
||||
if (tag == null) return;
|
||||
|
||||
iconFiles.forEach(({ status, file, renamedFile }) => {
|
||||
@@ -11,7 +11,7 @@ const iconMetaData = await getIconMetaData(path.resolve(scriptDir, '../../icons'
|
||||
const iconAliasesRedirectRoutes = Object.entries(iconMetaData)
|
||||
.filter(([, { aliases }]) => aliases?.length)
|
||||
.map(([iconName, { aliases }]) => {
|
||||
const aliasRouteMatches = aliases.map((alias) => alias.name).join('|');
|
||||
const aliasRouteMatches = aliases.map((alias) => typeof alias === 'string' ? alias : alias?.name).join('|');
|
||||
|
||||
return {
|
||||
src: `^/icons/(${aliasRouteMatches})$`,
|
||||
@@ -0,0 +1,104 @@
|
||||
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { loadEnvFile } from 'node:process';
|
||||
import { readSvgDirectory } from '@lucide/helpers';
|
||||
|
||||
const currentDir = process.cwd();
|
||||
const ICONS_DIR = path.resolve(currentDir, '../icons');
|
||||
const iconPopularityDirectory = path.resolve(currentDir, '.vitepress/data', 'iconPopularity');
|
||||
|
||||
try {
|
||||
// Load environment variables from .env file, if it exists.
|
||||
loadEnvFile(`${currentDir}/.env`);
|
||||
} catch (error) {}
|
||||
|
||||
if (fs.existsSync(iconPopularityDirectory)) {
|
||||
fs.rmSync(iconPopularityDirectory, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(iconPopularityDirectory)) {
|
||||
fs.mkdirSync(iconPopularityDirectory);
|
||||
}
|
||||
|
||||
const iconJsonFiles = await readSvgDirectory(ICONS_DIR, '.json');
|
||||
const iconNames = iconJsonFiles.map((file) => path.basename(file, '.json'));
|
||||
|
||||
const defaultIconNamePopularity: Record<string, number> = Object.fromEntries(
|
||||
iconNames.map((iconName) => [iconName, 0])
|
||||
);
|
||||
|
||||
interface AnalyticsResponse {
|
||||
results: {
|
||||
dimensions: [route: string];
|
||||
metrics: [count: number];
|
||||
}[];
|
||||
}
|
||||
|
||||
const writeIconPopularity = async (iconVisitCounts: Record<string, number>) => {
|
||||
const writePopularityPromises = Object.entries(iconVisitCounts).map(([iconName, count]) => {
|
||||
const location = path.resolve(iconPopularityDirectory, `${iconName}.json`);
|
||||
return fs.promises.writeFile(location, JSON.stringify({ count }, null, 2), 'utf-8');
|
||||
});
|
||||
|
||||
await Promise.all(writePopularityPromises);
|
||||
console.log('Successfully written icon popularity data for', writePopularityPromises.length, 'icons.');
|
||||
};
|
||||
|
||||
if (process.env.LUCIDE_ANALYTICS_TOKEN === undefined) {
|
||||
console.warn('LUCIDE_ANALYTICS_TOKEN environment variable is not set. Writing default icon popularity data.');
|
||||
await writeIconPopularity(defaultIconNamePopularity);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('https://analytics.lucide.dev/api/v2/query', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${process.env.LUCIDE_ANALYTICS_TOKEN}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"site_id": "lucide.dev",
|
||||
"metrics": [
|
||||
"visitors"
|
||||
],
|
||||
"dimensions": [
|
||||
"visit:entry_page"
|
||||
],
|
||||
"date_range": "12mo"
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const analyticsData: AnalyticsResponse = await response.json();
|
||||
|
||||
const iconVisitCounts = analyticsData.results.reduce((acc, { dimensions, metrics }) => {
|
||||
const [route] = dimensions;
|
||||
const [count] = metrics;
|
||||
|
||||
if (!route.startsWith('/icons')) {
|
||||
return acc
|
||||
}
|
||||
// Only match routes that start with /icons/ and end with an icon name, e.g. /icons/activity, /icons/activity/ or /icons/activity?ref=homepage
|
||||
const match = route.match(/^\/icons\/([^\/\?]+)(?:[\/\?]|$)/);
|
||||
if (match) {
|
||||
const iconName = match[1];
|
||||
|
||||
if (iconNames.includes(iconName)) {
|
||||
acc[iconName] = (acc[iconName] || 0) + count;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, defaultIconNamePopularity);
|
||||
|
||||
await writeIconPopularity(iconVisitCounts);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching analytics data:', error);
|
||||
await writeIconPopularity(defaultIconNamePopularity);
|
||||
}
|
||||
+11
-3
@@ -13,7 +13,7 @@
|
||||
"title": "Lucide Icons icon schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["$schema", "categories", "tags"],
|
||||
"required": ["$schema", "contributors", "categories", "tags", "use-cases"],
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
@@ -40,7 +40,7 @@
|
||||
}
|
||||
},
|
||||
"dependentRequired": {
|
||||
"deprecated": ["deprecationReason", "toBeRemovedInVersion"],
|
||||
"deprecated": ["deprecationReason"],
|
||||
"deprecationReason": ["deprecated"],
|
||||
"toBeRemovedInVersion": ["deprecated"]
|
||||
}
|
||||
@@ -114,6 +114,14 @@
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"use-cases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"minItems": 0,
|
||||
"uniqueItems": true
|
||||
},
|
||||
"deprecated": {
|
||||
"const": true
|
||||
},
|
||||
@@ -126,7 +134,7 @@
|
||||
}
|
||||
},
|
||||
"dependentRequired": {
|
||||
"deprecated": ["deprecationReason", "toBeRemovedInVersion"],
|
||||
"deprecated": ["deprecationReason"],
|
||||
"deprecationReason": ["deprecated"],
|
||||
"toBeRemovedInVersion": ["deprecated"]
|
||||
},
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"danielbayley",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Decreasing the font size in a text editor"
|
||||
],
|
||||
"tags": [
|
||||
"letter",
|
||||
"font size",
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"danielbayley",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Increasing the font size in a text editor"
|
||||
],
|
||||
"tags": [
|
||||
"letter",
|
||||
"font size",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"use-cases": [
|
||||
"indicating a large and small text size",
|
||||
"formatting text size",
|
||||
"indicating a heading and body text"
|
||||
],
|
||||
"contributors": [
|
||||
"it-is-not",
|
||||
"jguddas",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing physical disability or accessibility assistance",
|
||||
"Marking wheelchair-accessible facilities or routes"
|
||||
],
|
||||
"tags": [
|
||||
"disability",
|
||||
"disabled",
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"colebemis",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing an activity feed or recent actions",
|
||||
"Showing health metrics such as heart rate or vital signs",
|
||||
"Indicating live system activity or monitoring"
|
||||
],
|
||||
"tags": [
|
||||
"pulse",
|
||||
"action",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "../icon.schema.json",
|
||||
"contributors": [
|
||||
"ahtohbi4",
|
||||
"jamiemlaw",
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"marking content as sponsored",
|
||||
"linking to an advertise with us page"
|
||||
],
|
||||
"tags": [
|
||||
"advert",
|
||||
"affiliate",
|
||||
"brand",
|
||||
"campaign",
|
||||
"commercial",
|
||||
"marketing",
|
||||
"monetize",
|
||||
"paid",
|
||||
"partner",
|
||||
"promo",
|
||||
"sponsor"
|
||||
],
|
||||
"categories": [
|
||||
"multimedia",
|
||||
"notifications"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<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"
|
||||
>
|
||||
<path d="M10 13H6" />
|
||||
<path d="M10 15v-4a2 2 0 0 0-4 0v4" />
|
||||
<path d="M14 14.5a.5.5 0 0 0 .5.5h1a2.5 2.5 0 0 0 2.5-2.5v-1A2.5 2.5 0 0 0 15.5 9h-1a.5.5 0 0 0-.5.5z" />
|
||||
<rect x="2" y="5" width="20" height="14" rx="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 434 B |
@@ -4,6 +4,10 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing air conditioning or ventilation in smart home apps",
|
||||
"Indicating climate control amenities on hotel and booking platforms"
|
||||
],
|
||||
"tags": [
|
||||
"air conditioner",
|
||||
"ac",
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
"csandman",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Streaming or mirroring a screen to an external display",
|
||||
"Casting media to a TV or compatible device"
|
||||
],
|
||||
"tags": [
|
||||
"stream",
|
||||
"cast",
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Confirming an alarm or scheduled reminder is set",
|
||||
"Marking a timed task as complete"
|
||||
],
|
||||
"tags": [
|
||||
"done",
|
||||
"todo",
|
||||
@@ -22,8 +26,7 @@
|
||||
{
|
||||
"name": "alarm-check",
|
||||
"deprecationReason": "alias.name",
|
||||
"deprecated": true,
|
||||
"toBeRemovedInVersion": "v1.0"
|
||||
"deprecated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Removing or deleting an alarm"
|
||||
],
|
||||
"tags": [
|
||||
"remove"
|
||||
],
|
||||
@@ -18,8 +21,7 @@
|
||||
{
|
||||
"name": "alarm-minus",
|
||||
"deprecationReason": "alias.name",
|
||||
"deprecated": true,
|
||||
"toBeRemovedInVersion": "v1.0"
|
||||
"deprecated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Turning off or disabling an alarm"
|
||||
],
|
||||
"tags": [
|
||||
"morning",
|
||||
"turn-off"
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Adding or creating a new alarm"
|
||||
],
|
||||
"tags": [
|
||||
"add"
|
||||
],
|
||||
@@ -18,8 +21,7 @@
|
||||
{
|
||||
"name": "alarm-plus",
|
||||
"deprecationReason": "alias.name",
|
||||
"deprecated": true,
|
||||
"toBeRemovedInVersion": "v1.0"
|
||||
"deprecated": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"jguddas",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Setting an alarm or wake-up reminder"
|
||||
],
|
||||
"tags": [
|
||||
"morning"
|
||||
],
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing a smoke or fire detector in smart home apps",
|
||||
"Indicating fire safety equipment or amenities in listings"
|
||||
],
|
||||
"tags": [
|
||||
"fire",
|
||||
"alert",
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
"csandman",
|
||||
"mittalyashu"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing a photo album or collection",
|
||||
"Indicating a music album"
|
||||
],
|
||||
"tags": [
|
||||
"photo",
|
||||
"book"
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the horizontal center in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the vertical center in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the horizontal end in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"bottom",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the vertical end in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"right",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements evenly around their horizontal centers in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements by their right edges in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"right",
|
||||
"items",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"r4lv",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements by their left edges in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements around the horizontal center in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements to the horizontal end in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"right",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements to the horizontal start in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements with equal horizontal space around them in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements with equal horizontal space between them in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"around",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the horizontal start in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Aligning elements to the vertical start in page builders and design tools"
|
||||
],
|
||||
"tags": [
|
||||
"left",
|
||||
"items",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"ericfennis",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements evenly around the vertical center in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"items",
|
||||
"flex",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements toward the vertical end in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"bottom",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements toward the vertical start in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements around the vertical center in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements to the vertical end in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"bottom",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Justifying elements to the vertical start in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"top",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements with equal vertical space around them in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
"contributors": [
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Distributing elements with equal vertical space between them in design tools"
|
||||
],
|
||||
"tags": [
|
||||
"center",
|
||||
"items",
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"use-cases": [
|
||||
"Directing users to nearby emergency services in navigation apps",
|
||||
"Marking emergency contacts or medical transport services"
|
||||
],
|
||||
"tags": [
|
||||
"ambulance",
|
||||
"emergency",
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
"karsa-mistmere",
|
||||
"jguddas"
|
||||
],
|
||||
"use-cases": [
|
||||
"Inserting an ampersand in text editors",
|
||||
"Representing a join or concatenation operator in code"
|
||||
],
|
||||
"tags": [
|
||||
"and",
|
||||
"typography",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"danielbayley",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing a logical AND operator in code"
|
||||
],
|
||||
"tags": [
|
||||
"and",
|
||||
"operator",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing museological or archaeological artefacts",
|
||||
"Indicating pottery or ceramics",
|
||||
"Symbolizing Greek culture or antiquity",
|
||||
"Denoting amphora-aged wines"
|
||||
],
|
||||
"tags": [
|
||||
"pottery",
|
||||
"artifact",
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
"ericfennis",
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing a ship's anchor or maritime context",
|
||||
"Linking to an anchor point or section within a page"
|
||||
],
|
||||
"tags": [
|
||||
"ship"
|
||||
],
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"karsa-mistmere",
|
||||
"ericfennis"
|
||||
],
|
||||
"use-cases": [
|
||||
"Expressing strong dissatisfaction in feedback or rating scales",
|
||||
"Marking the lowest score on a contentment scale (very unhappy to very happy)"
|
||||
],
|
||||
"tags": [
|
||||
"emoji",
|
||||
"anger",
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"contributors": [
|
||||
"karsa-mistmere"
|
||||
],
|
||||
"use-cases": [
|
||||
"Expressing mild dissatisfaction in feedback or rating scales",
|
||||
"Marking an unhappy step on a contentment scale (very unhappy to very happy)"
|
||||
],
|
||||
"tags": [
|
||||
"emoji",
|
||||
"nuisance",
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
"contributors": [
|
||||
"danielbayley"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing signal reception or broadcast",
|
||||
"Indicating a TV antenna or terrestrial reception",
|
||||
"Showing connectivity or frequency tuning"
|
||||
],
|
||||
"tags": [
|
||||
"signal",
|
||||
"connection",
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
"karsa-mistmere",
|
||||
"danielbayley"
|
||||
],
|
||||
"use-cases": [
|
||||
"Representing construction, metalwork, or industrial tools"
|
||||
],
|
||||
"tags": [
|
||||
"metal",
|
||||
"iron",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user