Compare commits

..
Author SHA1 Message Date
Eric Fennis 80a2d2540d Merge branch 'main' of https://github.com/lucide-icons/lucide into adding-use-cases 2026-07-17 18:22:55 +02:00
Eric Fennis 8a3f8c6bb9 Merge branch 'main' of https://github.com/lucide-icons/lucide into adding-use-cases 2026-06-26 08:49:58 +02:00
Eric Fennis 27c997fc14 Add batch 12 2026-06-18 14:39:05 +02:00
Eric Fennis 622d9efa24 Add batch 11 2026-06-18 14:31:44 +02:00
Eric Fennis b61e540082 Add batch 10 2026-06-18 14:29:58 +02:00
Eric Fennis d21b575107 Add batch 9 2026-06-18 14:26:27 +02:00
Eric Fennis 32776e8242 Add batch 8 2026-06-18 14:16:31 +02:00
Eric Fennis bea6fdcac3 Add batch 7 2026-06-18 14:13:01 +02:00
Eric Fennis e86c474fd5 Add batch 6 2026-06-18 13:55:12 +02:00
Eric Fennis 6c36c9ba80 Add batch 5 2026-06-18 13:43:23 +02:00
Eric Fennis e8a2214eee Add batch 4 2026-06-18 13:38:01 +02:00
Eric Fennis 91cc79b19f Add batch 3 2026-06-18 13:34:32 +02:00
Eric Fennis df5fb69839 Add batch 2 2026-06-18 13:07:41 +02:00
Eric Fennis 9b5fd4514e Batch one of use-cases 2026-06-18 11:46:30 +02:00
Eric Fennis b380956554 Apply use-cases 2026-06-18 11:19:17 +02:00
Eric Fennis 1de10e927d Merge branch 'adding-use-cases-part-two' into adding-use-cases 2026-06-18 10:59:30 +02:00
Eric Fennis b01519fd54 Merge branch 'adding-use-cases-part-one' into adding-use-cases 2026-06-18 10:59:21 +02:00
Eric Fennis c1fcc1f238 ci(release.yml): Remove concurrency field to prevent release mess 2026-06-18 08:57:54 +02:00
Eric Fennis 8f2482ac06 Adding remaining use-cases 2026-06-13 10:41:16 +02:00
Eric Fennis c99099ea1c Adding use-cases from prs 2026-06-13 10:40:41 +02:00
2567 changed files with 7990 additions and 20842 deletions
-1
View File
@@ -9,4 +9,3 @@ strikethrough
touchpad
ungroup
toc
sunlounger
-2
View File
@@ -47,9 +47,7 @@ Common scopes: icons, docs, studio, site, dev
### Design <!-- ONLY for new icons -->
<!-- All of these requirements must be fulfilled. -->
- [ ] I've read and followed the [icon design guidelines](https://lucide.dev/contribute/icon-design-guide)
- [ ] I've made sure that the icons don't [already exist](https://lucide.dev/icons), including the [Lab directory](https://github.com/lucide-icons/lucide/tree/main/lab).
- [ ] I've made sure that the icons look sharp on low DPI displays.
- [ ] I've made sure that the icons look consistent with the icon set in size, optical volume and density.
- [ ] I've made sure that the icons are visually centered.
+4
View File
@@ -27,6 +27,10 @@ permissions:
id-token: write # Required for OIDC
contents: write
# This is added to make sure we wait until last release is done, this is needed for the font-build process
concurrency:
group: continuous-integration
jobs:
check-dispatch-gate:
if: github.repository == 'lucide-icons/lucide' &&
+3 -1
View File
@@ -1,7 +1,9 @@
name: Linting PR code
name: Linting PR
on:
pull_request:
paths-ignore:
- icons/*.svg
jobs:
lint-code:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Linting PR title
name: Linting PR
on:
pull_request:
+1 -29
View File
@@ -7,7 +7,6 @@ on:
pull_request:
paths:
- 'icons/*'
- 'lab/*'
jobs:
lint-filenames:
@@ -18,47 +17,20 @@ jobs:
contents: read
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
files: |
icons/*
lab/*
files: icons/*
- name: Generate annotations
run: node ./scripts/lintFilenames.mts
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
check-lab-icons:
name: Check Lab Icons
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
cache: 'pnpm'
node-version-file: 'package.json'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check lab icons do not already exist in icons
run: node ./scripts/checkLabIcons.mts
lint-aliases:
name: Check Uniqueness of Aliases
runs-on: ubuntu-latest
+2 -2
View File
@@ -1,6 +1,6 @@
name: Request Review
name: 'Request Review'
on:
pull_request:
pull_request_target:
types: [opened]
paths:
- icons/*.svg
-3
View File
@@ -54,9 +54,6 @@ docs/.vitepress/data/iconDetails
docs/.vitepress/data/relatedIcons.json
docs/.vitepress/data/brandStopwords.json
docs/.vitepress/data/iconPopularity
docs/.vitepress/data/lab/iconNodes
docs/.vitepress/data/lab/iconMetaData.ts
docs/.vitepress/data/lab/iconPopularity
docs/.vercel
docs/.nitro
docs/public/og/**
+1 -1
View File
@@ -30,7 +30,7 @@ Read it here: [ICON_GUIDELINES](https://lucide.dev/contribute/icon-design-guide)
#### Lucide Studio
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/?utm_source=lucide.dev&utm_medium=contributing-guide). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
For formatting and adjusting SVG icons, [@jguddas](https://github.com/jguddas) made a great tool called [Lucide Studio](https://studio.lucide.dev/). It is a web-based SVG editor that allows you to edit and adjust icons in the Lucide style. You can use it to create new icons or modify existing ones.
#### Editor guides
+5 -4
View File
@@ -56,6 +56,7 @@
"kubernetes": "Kubernetes",
"leetcode": "LeetCode",
"leet-code": "LeetCode",
"line": "LINE",
"linkedin": "LinkedIn",
"lua": "Lua",
"mariadb": "MariaDB",
@@ -68,7 +69,7 @@
"nestjs": "NestJS",
"netflix": "Netflix",
"netlify": "Netlify",
"nextjs": "Next.js",
"next": "Next.js",
"nodejs": "Node.js",
"notion": "Notion",
"nostr": "Nostr",
@@ -104,8 +105,7 @@
"shopify": "Shopify",
"skype": "Skype",
"slack": "Slack",
"snapchat": "Snapchat",
"solidjs": "SolidJS",
"solid": "SolidJS",
"soundcloud": "SoundCloud",
"spotify": "Spotify",
"sqlite": "SQLite",
@@ -144,5 +144,6 @@
"xbox": "Xbox",
"yarn": "Yarn",
"youtube": "YouTube",
"zig": "Zig"
"zig": "Zig",
"zoom": "Zoom"
}
@@ -1,25 +0,0 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../../data/lab/iconMetaData';
import iconPopularity from '../../../data/lab/iconPopularity';
import iconNodes from '../../../data/lab/iconNodes';
export default eventHandler((event) => {
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
return Object.fromEntries(
Object.entries(iconMetaData).map(([iconName, iconData]) => {
// Remove $schema from the response
delete iconData['$schema'];
return [
iconName,
{
name: iconName,
iconNode: iconNodes[iconName],
popularity: iconPopularity[iconName]?.count ?? 0,
...iconData,
},
];
}),
);
});
@@ -1,14 +0,0 @@
import { eventHandler, setResponseHeader } from 'h3';
import iconMetaData from '../../../data/lab/iconMetaData';
export default eventHandler((event) => {
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
for (const iconDetail in iconMetaData) {
// Remove $schema from the response
delete iconMetaData[iconDetail]['$schema'];
}
return iconMetaData;
});
@@ -1,30 +0,0 @@
import { eventHandler, getQuery, setResponseHeader } from 'h3';
import iconNodes from '../../../data/lab/iconNodes';
type IconNodeWithKeys = [elementName: string, attrs: Record<string, string> & { key?: string }][];
export default eventHandler((event) => {
const query = getQuery(event);
const withUniqueKeys = query.withUniqueKeys === 'true';
setResponseHeader(event, 'Cache-Control', 'public, max-age=86400');
setResponseHeader(event, 'Access-Control-Allow-Origin', '*');
if (withUniqueKeys) {
return iconNodes;
}
return Object.entries(iconNodes).reduce((acc, [name, iconNode]) => {
if (withUniqueKeys) {
return [name, iconNode];
}
const newIconNode = (iconNode as IconNodeWithKeys).map(([name, { key, ...attrs }]) => {
return [name, attrs];
});
acc[name] = newIconNode;
return acc;
}, {});
});
View File
@@ -84,7 +84,7 @@ const base64InnerSvg = computed(() => {
</g>
</svg>
<a
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}&utm_source=lucide.dev&utm_medium=icon-detail-preview`"
:href="`https://studio.lucide.dev/edit?value=${encodeURIComponent(base64InnerSvg)}&name=${name}`"
target="_blank"
rel="noopener noreferrer"
class="studio-button"
@@ -16,7 +16,7 @@ export const externalLibContext = {
};
const externalLibIconNodesAPI = {
lab: `${import.meta.env.DEV ? 'http://localhost:3000' : ''}/api/lab/icon-details`,
lab: 'https://lab.lucide.dev/api/icon-details',
};
export function useExternalLibs(): ExternalLibContext {
@@ -32,15 +32,13 @@ export function useExternalLibs(): ExternalLibContext {
newExternalIconNodes[lib] = savedIconNodes[lib];
} else {
const response = await fetch(externalLibIconNodesAPI[lib]);
const iconDetails = await response.json();
const iconNodes = await response.json();
if (iconDetails) {
newExternalIconNodes[lib] = Object.values(iconDetails).map((iconEntity: IconEntity) => {
return {
...iconEntity,
externalLibrary: lib,
};
});
if (iconNodes) {
newExternalIconNodes[lib] = Object.values(iconNodes).map((iconEntity: IconEntity) => ({
...iconEntity,
externalLibrary: lib,
}));
}
}
}
+1 -1
View File
@@ -158,7 +158,7 @@ For each icon these attributes are applied, corresponding to the above rules.
### Minify paths
The code of paths can sometimes get quite large. To reduce file size we like to minify the code.
We recommend to use [Lucide Studio](https://studio.lucide.dev/?utm_source=lucide.dev&utm_medium=design-guide) to tidy paths to 3 points of precision.
We recommend to use [Lucide Studio](https://studio.lucide.dev/) to tidy paths to 3 points of precision.
### Allowed elements
@@ -12,10 +12,6 @@ import Sandpack from '~/.vitepress/theme/components/editors/SandpackAngular.vue'
While they aren't provided as standalone components, they can be still be passed to the `LucideIcon` component the same way as official icons:
::: info Limitation
Only custom icons that follow Lucide's [code conventions](../../../contribute/icon-design-guide.md#code-conventions) are supported.
:::
### Directly as LucideIconData
::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular,@lucide/lab"}
+17 -19
View File
@@ -1,26 +1,24 @@
import iconNodes from '../../.vitepress/data/lab/iconNodes';
import iconPopularity from '../../.vitepress/data/lab/iconPopularity';
import iconMetaData from '../../.vitepress/data/lab/iconMetaData';
import { type IconEntity } from '../../.vitepress/theme/types';
import { IconEntity } from '../../.vitepress/theme/types';
export default {
paths: async () => {
return (Object.entries(iconNodes) as unknown as IconEntity[]).map(([name, iconNode]) => {
const iconDetails = iconMetaData[name];
try {
const iconDetailsResponse = await fetch('https://lab.lucide.dev/api/icon-details');
const iconDetails = (await iconDetailsResponse.json()) as Record<string, IconEntity>;
delete iconDetails['$schema'];
return Object.values(iconDetails).map((iconEntity) => {
const params = {
externalLibrary: 'lab',
...iconEntity,
};
const params = {
name,
iconNode,
externalLibrary: 'lab',
popularity: iconPopularity[name]?.count ?? 0,
...iconDetails,
};
return {
params,
};
});
return {
params,
};
});
} catch (error) {
console.error('Error fetching icon details:', error);
return [];
}
},
};
+5 -7
View File
@@ -8,21 +8,19 @@
"docs:dev": "pnpm run /^prebuild:.*/ && vitepress dev",
"docs:build": "pnpm run /^prebuild:.*/ && vitepress build",
"docs:preview": "vitepress preview",
"build:docs": "NODE_OPTIONS=--max-old-space-size=8192 vitepress build",
"build:docs": "vitepress build",
"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 && node ./scripts/writeiconPopularityIndex.mts",
"prebuild:iconPopularity": "node ./scripts/writeiconPopularity.mts",
"prebuild:brandStopwords": "node ./scripts/writeBrandStopwords.mts",
"prebuild:writeLabIconNodes": "node ./scripts/writeLabIconNodes.mts",
"prebuild:writeLabIconMetaIndex": "node ./scripts/writeLabIconMetaIndex.mts",
"postbuild:vercelJson": "node ./scripts/writeVercelOutput.mts",
"dev": "nitropack dev",
"prebuild:api": "nitropack prepare",
"build:api": "NODE_OPTIONS=--max-old-space-size=8192 nitropack build",
"dev": "npx nitropack dev",
"prebuild:api": "npx nitropack prepare",
"build:api": "npx nitropack build",
"build": "pnpm run /^prebuild:.*/ && pnpm run build:api && pnpm run build:docs && pnpm postbuild:vercelJson",
"preview": "node .output/server/index.mjs"
},
+7 -7
View File
@@ -23,12 +23,12 @@ const writeIconFiles = icons.map(async (iconFileName) => {
const location = path.resolve(iconDetailsDirectory, `${iconName}.ts`);
const contents = `\
import iconNode from '../iconNodes/${iconName}.node.json' with { type: 'json' };
import metaData from '../../../../icons/${iconName}.json' with { type: 'json' };
import releaseData from '../releaseMetadata/${iconName}.json' with { type: 'json' };
import popularity from '../iconPopularity/${iconName}.json' with { type: 'json' };
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;
const { tags, categories, contributors, aliases, deprecated, deprecationReason, toBeRemovedInVersion } = metaData
const iconDetails = {
name: '${iconName}',
@@ -42,9 +42,9 @@ const iconDetails = {
deprecationReason,
toBeRemovedInVersion,
...releaseData,
};
}
export default iconDetails;
export default iconDetails
`;
await fs.promises.writeFile(location, contents, 'utf-8');
-49
View File
@@ -1,49 +0,0 @@
import fs from 'fs';
import path from 'path';
import { readSvgDirectory, toCamelCase } from '@lucide/helpers';
const currentDir = process.cwd();
const ICONS_DIR = path.resolve(currentDir, '../lab');
const iconJsonFiles = await readSvgDirectory(ICONS_DIR, '.json');
const labDirectory = path.resolve(currentDir, '.vitepress/data/lab');
const location = path.resolve(currentDir, labDirectory, 'iconMetaData.ts');
if (fs.existsSync(location)) {
fs.unlinkSync(location);
}
if (!fs.existsSync(labDirectory)) {
fs.mkdirSync(labDirectory);
}
const iconMetaIndexFileImports = [];
const iconMetaIndexFileExports = [];
iconJsonFiles.forEach((iconJsonFile) => {
const iconName = path.basename(iconJsonFile, '.json');
iconMetaIndexFileImports.push(
`import ${toCamelCase(iconName)}Metadata from '../../../../lab/${iconName}.json';`,
);
iconMetaIndexFileExports.push(` '${iconName}': ${toCamelCase(iconName)}Metadata,`);
});
try {
await fs.promises.writeFile(
location,
`\
${iconMetaIndexFileImports.join('\n')}
export default {
${iconMetaIndexFileExports.join('\n')}
}
`,
'utf-8',
);
console.log('Successfully write icon json file index');
} catch (error) {
throw new Error(`Something went wrong generating icon json file index file,\n ${error}`);
}
-62
View File
@@ -1,62 +0,0 @@
import fs from 'fs';
import path from 'path';
import { renderIconsObject } from '@lucide/build-icons';
import { readSvgDirectory, toCamelCase } from '@lucide/helpers';
const currentDir = process.cwd();
const ICONS_DIR = path.resolve(currentDir, '../lab');
const svgFiles = await readSvgDirectory(ICONS_DIR);
const icons = await renderIconsObject(svgFiles, ICONS_DIR, true);
const iconNodesDirectory = path.resolve(currentDir, '.vitepress/data/lab', 'iconNodes');
if (fs.existsSync(iconNodesDirectory)) {
fs.rmSync(iconNodesDirectory, { recursive: true, force: true });
}
if (!fs.existsSync(iconNodesDirectory)) {
fs.mkdirSync(iconNodesDirectory);
}
const iconIndexFile = path.resolve(iconNodesDirectory, `index.ts`);
const iconIndexFileImports: string[] = [];
const iconIndexFileExports: string[] = [];
const iconIndexFileDefaultExports: string[] = [];
const writeIconFiles = Object.entries(icons).map(async ([iconName, { children }]) => {
// We need to use .node.json because the there is a file called package, which is a reserved word for packages.
const location = path.resolve(iconNodesDirectory, `${iconName}.node.json`);
const iconNode = children.map(({ name, attributes }) => [name, attributes]);
const output = JSON.stringify(iconNode, null, 2);
await fs.promises.writeFile(location, output, 'utf-8');
iconIndexFileImports.push(
`import ${toCamelCase(iconName)}Node from './${iconName}.node.json' with { type: "json" };`,
);
iconIndexFileExports.push(` ${toCamelCase(iconName)}Node as ${toCamelCase(iconName)},`);
iconIndexFileDefaultExports.push(` '${iconName}': ${toCamelCase(iconName)}Node,`);
});
try {
await Promise.all(writeIconFiles);
await fs.promises.writeFile(
iconIndexFile,
`\
${iconIndexFileImports.join('\n')}
export {
${iconIndexFileExports.join('\n')}
}
export default {
${iconIndexFileDefaultExports.join('\n')}
}
`,
'utf-8',
);
console.log('Successfully write', writeIconFiles.length, 'iconNodes.');
} catch (error) {
throw new Error(`Something went wrong generating iconNode files,\n ${error}`);
}
+28 -76
View File
@@ -5,59 +5,27 @@ import { loadEnvFile } from 'node:process';
import { readSvgDirectory } from '@lucide/helpers';
const currentDir = process.cwd();
const dataDirectory = path.resolve(currentDir, '.vitepress/data');
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) {}
interface IconCollection {
/** Human readable name, used for logging. */
name: string;
/** Directory containing the icon `.json` files. */
iconsDir: string;
/** Directory the `${iconName}.json` popularity files are written to. */
outputDir: string;
/** Matches an analytics route and captures the icon name in group 1. */
routePattern: RegExp;
if (fs.existsSync(iconPopularityDirectory)) {
fs.rmSync(iconPopularityDirectory, { recursive: true, force: true });
}
// Lab must come before the default collection because `/icons/lab/foo` also
// matches the default `/icons/(...)` pattern; the first match wins per route.
const iconCollections: IconCollection[] = [
{
name: 'lab',
iconsDir: path.resolve(currentDir, '../lab'),
outputDir: path.resolve(dataDirectory, 'lab', 'iconPopularity'),
// e.g. /icons/lab/burger, /icons/lab/burger/ or /icons/lab/burger?ref=homepage
routePattern: /^\/icons\/lab\/([^\/\?]+)(?:[\/\?]|$)/,
},
{
name: 'default',
iconsDir: path.resolve(currentDir, '../icons'),
outputDir: path.resolve(dataDirectory, 'iconPopularity'),
// e.g. /icons/activity, /icons/activity/ or /icons/activity?ref=homepage
routePattern: /^\/icons\/([^\/\?]+)(?:[\/\?]|$)/,
},
];
interface CollectionData extends IconCollection {
iconNames: string[];
counts: Record<string, number>;
if (!fs.existsSync(iconPopularityDirectory)) {
fs.mkdirSync(iconPopularityDirectory);
}
const collections: CollectionData[] = await Promise.all(
iconCollections.map(async (collection) => {
const iconJsonFiles = await readSvgDirectory(collection.iconsDir, '.json');
const iconNames = iconJsonFiles.map((file) => path.basename(file, '.json'));
const iconJsonFiles = await readSvgDirectory(ICONS_DIR, '.json');
const iconNames = iconJsonFiles.map((file) => path.basename(file, '.json'));
return {
...collection,
iconNames,
counts: Object.fromEntries(iconNames.map((iconName) => [iconName, 0])),
};
}),
const defaultIconNamePopularity: Record<string, number> = Object.fromEntries(
iconNames.map((iconName) => [iconName, 0])
);
interface AnalyticsResponse {
@@ -67,30 +35,19 @@ interface AnalyticsResponse {
}[];
}
const writeIconPopularity = async ({ name, outputDir, counts }: CollectionData) => {
if (fs.existsSync(outputDir)) {
fs.rmSync(outputDir, { recursive: true, force: true });
}
fs.mkdirSync(outputDir, { recursive: true });
const writePopularityPromises = Object.entries(counts).map(([iconName, count]) => {
const location = path.resolve(outputDir, `${iconName}.json`);
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,
`${name} icons.`,
);
console.log('Successfully written icon popularity data for', writePopularityPromises.length, 'icons.');
};
const writeAllIconPopularity = () => Promise.all(collections.map(writeIconPopularity));
if (process.env.LUCIDE_ANALYTICS_TOKEN === undefined) {
console.warn('LUCIDE_ANALYTICS_TOKEN environment variable is not set. Writing default icon popularity data.');
await writeAllIconPopularity();
await writeIconPopularity(defaultIconNamePopularity);
process.exit(0);
}
@@ -119,34 +76,29 @@ try {
const analyticsData: AnalyticsResponse = await response.json();
for (const { dimensions, metrics } of analyticsData.results) {
const iconVisitCounts = analyticsData.results.reduce((acc, { dimensions, metrics }) => {
const [route] = dimensions;
const [count] = metrics;
if (!route.startsWith('/icons')) {
continue;
return acc
}
// Route each visit to the first collection whose pattern matches. Lab is
// checked first so `/icons/lab/foo` is not swallowed by the default pattern.
for (const collection of collections) {
const match = route.match(collection.routePattern);
if (!match) {
continue;
}
// 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 (collection.iconNames.includes(iconName)) {
collection.counts[iconName] += count;
if (iconNames.includes(iconName)) {
acc[iconName] = (acc[iconName] || 0) + count;
}
break;
}
}
await writeAllIconPopularity();
return acc;
}, defaultIconNamePopularity);
await writeIconPopularity(iconVisitCounts);
} catch (error) {
console.error('Error fetching analytics data:', error);
await writeAllIconPopularity();
await writeIconPopularity(defaultIconNamePopularity);
}
-49
View File
@@ -1,49 +0,0 @@
import fs from 'fs';
import path from 'path';
import { readSvgDirectory, toCamelCase } from '@lucide/helpers';
const currentDir = process.cwd();
const ICONS_DIR = path.resolve(currentDir, '../lab');
const iconJsonFiles = await readSvgDirectory(ICONS_DIR, '.json');
const labIconPopularityDirectory = path.resolve(currentDir, '.vitepress/data/lab/iconPopularity');
const location = path.resolve(currentDir, labIconPopularityDirectory, 'index.ts');
if (fs.existsSync(location)) {
fs.unlinkSync(location);
}
if (!fs.existsSync(labIconPopularityDirectory)) {
fs.mkdirSync(labIconPopularityDirectory);
}
const iconMetaIndexFileImports = [];
const iconMetaIndexFileExports = [];
iconJsonFiles.forEach((iconJsonFile) => {
const iconName = path.basename(iconJsonFile, '.json');
iconMetaIndexFileImports.push(
`import ${toCamelCase(iconName)}Popularity from './${iconName}.json' with { type: 'json' };`,
);
iconMetaIndexFileExports.push(` '${iconName}': ${toCamelCase(iconName)}Popularity,`);
});
try {
await fs.promises.writeFile(
location,
`\
${iconMetaIndexFileImports.join('\n')}
export default {
${iconMetaIndexFileExports.join('\n')}
}
`,
'utf-8',
);
console.log('Successfully write icon json file index');
} catch (error) {
throw new Error(`Something went wrong generating icon json file index file,\n ${error}`);
}
+3 -1
View File
@@ -6,7 +6,9 @@
"danielbayley",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Decreasing the font size in a text editor"
],
"tags": [
"letter",
"font size",
+3 -1
View File
@@ -6,7 +6,9 @@
"danielbayley",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Increasing the font size in a text editor"
],
"tags": [
"letter",
"font size",
+1 -1
View File
@@ -13,4 +13,4 @@
<path d="M15.697 14h5.606" />
<path d="m2 16 4.039-9.69a.5.5 0 0 1 .923 0L11 16" />
<path d="M3.304 13h6.392" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 391 B

After

Width:  |  Height:  |  Size: 390 B

+4 -1
View File
@@ -4,7 +4,10 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Representing physical disability or accessibility assistance",
"Marking wheelchair-accessible facilities or routes"
],
"tags": [
"disability",
"disabled",
+5 -1
View File
@@ -4,7 +4,11 @@
"colebemis",
"jguddas"
],
"use-cases": [],
"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",
+1 -6
View File
@@ -21,15 +21,10 @@
"paid",
"partner",
"promo",
"sponsor",
"audio description",
"video description",
"described video",
"visual description"
"sponsor"
],
"categories": [
"multimedia",
"accessibility",
"notifications"
]
}
+4 -1
View File
@@ -4,7 +4,10 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Representing air conditioning or ventilation in smart home apps",
"Indicating climate control amenities on hotel and booking platforms"
],
"tags": [
"air conditioner",
"ac",
+4 -1
View File
@@ -5,7 +5,10 @@
"csandman",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Streaming or mirroring a screen to an external display",
"Casting media to a TV or compatible device"
],
"tags": [
"stream",
"cast",
+4 -1
View File
@@ -6,7 +6,10 @@
"jguddas",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Confirming an alarm or scheduled reminder is set",
"Marking a timed task as complete"
],
"tags": [
"done",
"todo",
+3 -1
View File
@@ -6,7 +6,9 @@
"jguddas",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Removing or deleting an alarm"
],
"tags": [
"remove"
],
+3 -1
View File
@@ -6,7 +6,9 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Turning off or disabling an alarm"
],
"tags": [
"morning",
"turn-off"
+3 -1
View File
@@ -6,7 +6,9 @@
"jguddas",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Adding or creating a new alarm"
],
"tags": [
"add"
],
+3 -1
View File
@@ -7,7 +7,9 @@
"jguddas",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Setting an alarm or wake-up reminder"
],
"tags": [
"morning"
],
+4 -1
View File
@@ -5,7 +5,10 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Representing a smoke or fire detector in smart home apps",
"Indicating fire safety equipment or amenities in listings"
],
"tags": [
"fire",
"alert",
+4 -1
View File
@@ -5,7 +5,10 @@
"csandman",
"mittalyashu"
],
"use-cases": [],
"use-cases": [
"Representing a photo album or collection",
"Indicating a music album"
],
"tags": [
"photo",
"book"
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the horizontal center in page builders and design tools"
],
"tags": [
"items",
"flex",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the vertical center in page builders and design tools"
],
"tags": [
"items",
"flex",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the horizontal end in page builders and design tools"
],
"tags": [
"items",
"bottom",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the vertical end in page builders and design tools"
],
"tags": [
"items",
"right",
@@ -4,7 +4,9 @@
"r4lv",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements evenly around their horizontal centers in design tools"
],
"tags": [
"items",
"flex",
+3 -1
View File
@@ -4,7 +4,9 @@
"r4lv",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements by their right edges in design tools"
],
"tags": [
"right",
"items",
+3 -1
View File
@@ -4,7 +4,9 @@
"r4lv",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements by their left edges in design tools"
],
"tags": [
"left",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements around the horizontal center in design tools"
],
"tags": [
"center",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements to the horizontal end in design tools"
],
"tags": [
"right",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements to the horizontal start in design tools"
],
"tags": [
"left",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements with equal horizontal space around them in design tools"
],
"tags": [
"center",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements with equal horizontal space between them in design tools"
],
"tags": [
"around",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the horizontal start in page builders and design tools"
],
"tags": [
"top",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Aligning elements to the vertical start in page builders and design tools"
],
"tags": [
"left",
"items",
+3 -1
View File
@@ -4,7 +4,9 @@
"ericfennis",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Distributing elements evenly around the vertical center in design tools"
],
"tags": [
"items",
"flex",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements toward the vertical end in design tools"
],
"tags": [
"bottom",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements toward the vertical start in design tools"
],
"tags": [
"top",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements around the vertical center in design tools"
],
"tags": [
"center",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements to the vertical end in design tools"
],
"tags": [
"bottom",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Justifying elements to the vertical start in design tools"
],
"tags": [
"top",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements with equal vertical space around them in design tools"
],
"tags": [
"center",
"items",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Distributing elements with equal vertical space between them in design tools"
],
"tags": [
"center",
"items",
+4 -1
View File
@@ -11,7 +11,10 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Directing users to nearby emergency services in navigation apps",
"Marking emergency contacts or medical transport services"
],
"tags": [
"ambulance",
"emergency",
+3 -2
View File
@@ -11,9 +11,10 @@
>
<path d="M10 10H6" />
<path d="M14 18V6a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v11a1 1 0 0 0 1 1h2" />
<path d="M19 18h2a1 1 0 0 0 1-1v-3.28a1 1 0 0 0-.684-.948l-1.923-.641a1 1 0 0 1-.578-.502l-1.539-3.076A1 1 0 0 0 16.382 8H14" />
<path
d="M19 18h2a1 1 0 0 0 1-1v-3.28a1 1 0 0 0-.684-.948l-1.923-.641a1 1 0 0 1-.578-.502l-1.539-3.076A1 1 0 0 0 16.382 8H14" />
<path d="M8 8v4" />
<path d="M9 18h6" />
<circle cx="17" cy="18" r="2" />
<circle cx="7" cy="18" r="2" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 553 B

+4 -1
View File
@@ -5,7 +5,10 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Inserting an ampersand in text editors",
"Representing a join or concatenation operator in code"
],
"tags": [
"and",
"typography",
+3 -1
View File
@@ -4,7 +4,9 @@
"danielbayley",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Representing a logical AND operator in code"
],
"tags": [
"and",
"operator",
+6 -1
View File
@@ -3,7 +3,12 @@
"contributors": [
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Representing museological or archaeological artefacts",
"Indicating pottery or ceramics",
"Symbolizing Greek culture or antiquity",
"Denoting amphora-aged wines"
],
"tags": [
"pottery",
"artifact",
+4 -1
View File
@@ -6,7 +6,10 @@
"ericfennis",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Representing a ship's anchor or maritime context",
"Linking to an anchor point or section within a page"
],
"tags": [
"ship"
],
+4 -1
View File
@@ -4,7 +4,10 @@
"karsa-mistmere",
"ericfennis"
],
"use-cases": [],
"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",
+4 -1
View File
@@ -3,7 +3,10 @@
"contributors": [
"karsa-mistmere"
],
"use-cases": [],
"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",
+5 -1
View File
@@ -3,7 +3,11 @@
"contributors": [
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Representing signal reception or broadcast",
"Indicating a TV antenna or terrestrial reception",
"Showing connectivity or frequency tuning"
],
"tags": [
"signal",
"connection",
+3 -1
View File
@@ -4,7 +4,9 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Representing construction, metalwork, or industrial tools"
],
"tags": [
"metal",
"iron",
+4 -1
View File
@@ -4,7 +4,10 @@
"colebemis",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Representing camera aperture or exposure settings",
"Indicating photography or capture features"
],
"tags": [
"camera",
"photo",
+4 -1
View File
@@ -3,7 +3,10 @@
"contributors": [
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Representing an application window on macOS",
"Indicating a desktop app or pane"
],
"tags": [
"application",
"menu bar",
+4 -1
View File
@@ -4,7 +4,10 @@
"danielbayley",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Representing an application window",
"Indicating a desktop app or executable"
],
"tags": [
"application",
"menu bar",
+5 -1
View File
@@ -3,7 +3,11 @@
"contributors": [
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Representing fruit or healthy eating",
"Categorizing produce or groceries",
"Symbolizing nutrition or a balanced diet"
],
"tags": [
"fruit",
"food",
+3 -1
View File
@@ -5,7 +5,9 @@
"ericfennis",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Restoring an item from the archive, reversing an archive action"
],
"tags": [
"unarchive",
"index",
+4 -1
View File
@@ -3,7 +3,10 @@
"contributors": [
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Removing an item from the archive",
"Marking archived mail as junk"
],
"tags": [
"index",
"backup",
+4 -1
View File
@@ -6,7 +6,10 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Archiving items or moving them to storage",
"Representing backups or stored records"
],
"tags": [
"index",
"backup",
+4 -1
View File
@@ -4,7 +4,10 @@
"karsa-mistmere",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a lounge or seating area in hotel and booking apps",
"Representing living room furniture in smart home applications"
],
"tags": [
"sofa",
"furniture",
+4 -1
View File
@@ -6,7 +6,10 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Moving down with emphasis, e.g. as a game control",
"Indicating a download or a jump to the bottom"
],
"tags": [
"backwards",
"reverse",
+3 -1
View File
@@ -8,7 +8,9 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a strong downward direction, e.g. as a game control"
],
"tags": [
"backwards",
"reverse",
+3 -1
View File
@@ -6,7 +6,9 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a strong leftward direction or turn, e.g. as a game control"
],
"tags": [
"previous",
"back",
+3 -1
View File
@@ -9,7 +9,9 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a strong leftward direction, e.g. as a game control"
],
"tags": [
"previous",
"back",
+3 -1
View File
@@ -6,7 +6,9 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a strong rightward direction or turn, e.g. as a game control"
],
"tags": [
"next",
"forward",
+3 -1
View File
@@ -9,7 +9,9 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Indicating a strong rightward direction, e.g. as a game control"
],
"tags": [
"next",
"forward",
+4 -1
View File
@@ -6,7 +6,10 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Representing the Caps Lock key on a keyboard",
"Indicating a speed boost or strong upward direction in games"
],
"tags": [
"caps lock",
"capitals",
+4 -1
View File
@@ -8,7 +8,10 @@
"jamiemlaw",
"jguddas"
],
"use-cases": [],
"use-cases": [
"Representing the Shift key on a keyboard",
"Indicating a strong upward direction in games"
],
"tags": [
"shift",
"keyboard",
+5 -2
View File
@@ -4,7 +4,9 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Sorting numerical values in ascending order"
],
"tags": [
"filter",
"sort",
@@ -26,6 +28,7 @@
"name": "arrow-down-01",
"deprecationReason": "alias.name",
"deprecated": true
}
},
"arrow-down-01"
]
}
+5 -2
View File
@@ -4,7 +4,9 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Sorting numerical values in descending order"
],
"tags": [
"filter",
"sort",
@@ -26,6 +28,7 @@
"name": "arrow-down-10",
"deprecationReason": "alias.name",
"deprecated": true
}
},
"arrow-down-10"
]
}
+5 -2
View File
@@ -4,7 +4,9 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Sorting items alphabetically from A to Z"
],
"tags": [
"filter",
"sort",
@@ -26,6 +28,7 @@
"name": "arrow-down-az",
"deprecationReason": "alias.name",
"deprecated": true
}
},
"arrow-down-az"
]
}
+4 -1
View File
@@ -4,7 +4,10 @@
"danielbayley",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Downloading or moving content downward from a boundary",
"Expanding a section downward"
],
"tags": [
"backwards",
"reverse",
+3 -1
View File
@@ -4,7 +4,9 @@
"colebemis",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Indicating a downward-left diagonal direction"
],
"tags": [
"direction",
"south-west",
+3 -1
View File
@@ -3,7 +3,9 @@
"contributors": [
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Sorting items from narrow to wide, e.g. smallest to largest"
],
"tags": [
"filter",
"sort",
+3 -1
View File
@@ -4,7 +4,9 @@
"colebemis",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Indicating a downward-right diagonal direction"
],
"tags": [
"direction",
"south-east",
+4 -1
View File
@@ -3,7 +3,10 @@
"contributors": [
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Moving down to a specific point or waypoint",
"Indicating a target or destination below"
],
"tags": [
"direction",
"south",
+4 -1
View File
@@ -4,7 +4,10 @@
"danielbayley",
"ericfennis"
],
"use-cases": [],
"use-cases": [
"Downloading or saving content to a boundary",
"Representing a git pull or collapsing a section"
],
"tags": [
"behind",
"direction",
+4 -1
View File
@@ -3,7 +3,10 @@
"contributors": [
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Indicating two-way data transfer or network traffic",
"Sorting or reordering items"
],
"tags": [
"bidirectional",
"two-way",
+3 -1
View File
@@ -5,7 +5,9 @@
"csandman",
"karsa-mistmere"
],
"use-cases": [],
"use-cases": [
"Sorting items from wide to narrow, e.g. largest to smallest"
],
"tags": [
"filter",
"sort",
+5 -2
View File
@@ -4,7 +4,9 @@
"karsa-mistmere",
"danielbayley"
],
"use-cases": [],
"use-cases": [
"Sorting items alphabetically from Z to A"
],
"tags": [
"filter",
"sort",
@@ -27,6 +29,7 @@
"name": "arrow-down-za",
"deprecationReason": "alias.name",
"deprecated": true
}
},
"arrow-down-za"
]
}

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