diff --git a/packages/lucide-icons/.npmignore b/packages/lucide-icons/.npmignore new file mode 100644 index 000000000..fc28543eb --- /dev/null +++ b/packages/lucide-icons/.npmignore @@ -0,0 +1,10 @@ +stats +node_modules +tests +scripts +build +src +babel.config.js +jest.config.js +rollup.config.js +yarn.error.log diff --git a/packages/lucide-icons/README.md b/packages/lucide-icons/README.md new file mode 100644 index 000000000..d034cd2c1 --- /dev/null +++ b/packages/lucide-icons/README.md @@ -0,0 +1,83 @@ +

+ + Lucide icon library for web applications. + +

+ +

+Lucide icon library for web applications. +

+ +
+ + [![npm](https://img.shields.io/npm/v/lucide?color=blue)](https://www.npmjs.com/package/lucide) + ![NPM Downloads](https://img.shields.io/npm/dw/lucide) + [![GitHub](https://img.shields.io/github/license/lucide-icons/lucide)](https://lucide.dev/license) +
+ +

+ About + · + Icons + · + Documentation + · + License +

+ +# Lucide + +Implementation of the lucide icon library for web applications. + +## Installation + +```sh +pnpm add lucide +``` + +```sh +npm install lucide +``` + +```sh +yarn add lucide +``` + +```sh +bun add lucide +``` + +### CDN + +```html + + + + + +``` + +## Documentation + +For full documentation, visit [lucide.dev](https://lucide.dev/guide/packages/lucide) + +## Community + +Join the [Discord server](https://discord.gg/EH6nSts) to chat with the maintainers and other users. + +## License + +Lucide is licensed under the ISC license. See [LICENSE](https://lucide.dev/license). + +## Sponsors + + + Powered by Vercel + + +DigitalOcean Referral Badge + +### Awesome backers 🍺 + +Scipress sponsor badge +pdfme sponsor badge diff --git a/packages/lucide-icons/package.json b/packages/lucide-icons/package.json new file mode 100644 index 000000000..e13cf5b4e --- /dev/null +++ b/packages/lucide-icons/package.json @@ -0,0 +1,57 @@ +{ + "name": "@lucide/icons", + "description": "A Lucide icon library that contains icon data in a standard Lucide format.", + "version": "0.0.1", + "license": "ISC", + "homepage": "https://lucide.dev", + "bugs": "https://github.com/lucide-icons/lucide/issues", + "repository": { + "type": "git", + "url": "https://github.com/lucide-icons/lucide.git", + "directory": "packages/lucide" + }, + "keywords": [ + "Lucide", + "HTML", + "Feather", + "Icons", + "Icon", + "SVG", + "Feather Icons", + "Fontawesome", + "Font Awesome" + ], + "amdName": "lucide-icons", + "source": "src/lucide-icons.js", + "main": "dist/cjs/lucide-icons.js", + "main:umd": "dist/umd/lucide-icons.js", + "module": "dist/esm/lucide-icons.js", + "unpkg": "dist/umd/lucide-icons.min.js", + "typings": "dist/lucide-icons.d.ts", + "sideEffects": false, + "files": [ + "dist" + ], + "scripts": { + "build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundle", + "copy:license": "cp ../../LICENSE ./LICENSE", + "clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts", + "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --withDynamicImports --separateAliasesFile --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts", + "build:bundle": "rollup -c rollup.config.mjs", + "test": "pnpm build:icons && vitest run", + "test:watch": "vitest watch", + "version": "pnpm version --git-tag-version=false" + }, + "devDependencies": { + "@lucide/build-icons": "workspace:*", + "@lucide/rollup-plugins": "workspace:*", + "@rollup/plugin-replace": "^6.0.1", + "@testing-library/jest-dom": "^6.1.6", + "jest-serializer-html": "^7.1.0", + "rollup": "^4.22.4", + "rollup-plugin-dts": "^6.1.0", + "typescript": "^4.9.3", + "vite": "5.4.14", + "vitest": "^1.1.1" + } +} diff --git a/packages/lucide-icons/rollup.config.mjs b/packages/lucide-icons/rollup.config.mjs new file mode 100644 index 000000000..a4840c024 --- /dev/null +++ b/packages/lucide-icons/rollup.config.mjs @@ -0,0 +1,83 @@ +import plugins from '@lucide/rollup-plugins'; +import replace from '@rollup/plugin-replace'; +import dts from 'rollup-plugin-dts'; +import pkg from './package.json' with { type: 'json' }; + +const outputFileName = pkg.name; +const outputDir = 'dist'; +const inputs = ['src/lucide.ts']; +const bundles = [ + { + format: 'umd', + inputs, + outputDir, + minify: true, + }, + { + format: 'umd', + inputs, + outputDir, + }, + { + format: 'cjs', + inputs, + outputDir, + }, + { + format: 'esm', + inputs, + outputDir, + preserveModules: true, + }, +]; + +const configs = bundles + .map(({ inputs, outputDir, format, minify, preserveModules }) => + inputs.map((input) => ({ + input, + plugins: [ + // This is for lucide plugin to replace an argument in createIcons so it is easier to use with UMD. + ...(format === 'umd' + ? [ + replace({ + 'icons = {}': 'icons = iconAndAliases', + delimiters: ['', ''], + preventAssignment: false, + }), + ] + : []), + ...plugins({ pkg, minify }), + ], + output: { + name: outputFileName, + ...(preserveModules + ? { + dir: `${outputDir}/${format}`, + } + : { + file: `${outputDir}/${format}/${outputFileName}${minify ? '.min' : ''}.js`, + }), + format, + sourcemap: true, + preserveModules, + }, + })), + ) + .flat(); + +const typesFileConfig = { + input: inputs[0], + output: [ + { + file: `${outputDir}/${outputFileName}.d.ts`, + format: 'esm', + }, + ], + plugins: [ + dts({ + include: ['src'], + }), + ], +}; + +export default [...configs, typesFileConfig]; diff --git a/packages/lucide-icons/scripts/exportTemplate.mjs b/packages/lucide-icons/scripts/exportTemplate.mjs new file mode 100644 index 000000000..63cc3bb0d --- /dev/null +++ b/packages/lucide-icons/scripts/exportTemplate.mjs @@ -0,0 +1,36 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import base64SVG from '@lucide/build-icons/utils/base64SVG.mjs'; + +export default async ({ + componentName, + iconName, + children, + getSvg, + deprecated, + deprecationReason, +}) => { + const svgContents = await getSvg(); + const svgBase64 = base64SVG(svgContents); + + return ` +import type { LucideIconData } from '../types'; + +/** + * @name ${iconName} + * @description Lucide SVG icon node. + * + * @preview ![img](data:image/svg+xml;base64,${svgBase64}) - https://lucide.dev/icons/${iconName} + * @see https://lucide.dev/guide/packages/lucide - Documentation + * + * @returns {Array} + * ${deprecated ? `@deprecated ${deprecationReason}` : ''} + */ +const ${componentName}: LucideIconData = ${JSON.stringify({ + name: iconName, + size: 24, + node: children, + })} + +export default ${componentName}; +`; +}; diff --git a/packages/lucide-icons/src/aliases/index.ts b/packages/lucide-icons/src/aliases/index.ts new file mode 100644 index 000000000..bbe66d0a2 --- /dev/null +++ b/packages/lucide-icons/src/aliases/index.ts @@ -0,0 +1,3 @@ +export * from './aliases'; +export * from './prefixed'; +export * from './suffixed'; diff --git a/packages/lucide-icons/src/defaultAttributes.ts b/packages/lucide-icons/src/defaultAttributes.ts new file mode 100644 index 000000000..98ba5dc04 --- /dev/null +++ b/packages/lucide-icons/src/defaultAttributes.ts @@ -0,0 +1,12 @@ +import { SVGProps } from './types'; + +const defaultAttributes: SVGProps = { + xmlns: 'http://www.w3.org/2000/svg', + fill: 'none', + stroke: 'currentColor', + 'stroke-width': 2, + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', +}; + +export default defaultAttributes; diff --git a/packages/lucide-icons/src/icons/.gitkeep b/packages/lucide-icons/src/icons/.gitkeep new file mode 100644 index 000000000..e3d85f774 --- /dev/null +++ b/packages/lucide-icons/src/icons/.gitkeep @@ -0,0 +1 @@ +Folder for generated icons diff --git a/packages/lucide-icons/src/iconsAndAliases.ts b/packages/lucide-icons/src/iconsAndAliases.ts new file mode 100644 index 000000000..098bba469 --- /dev/null +++ b/packages/lucide-icons/src/iconsAndAliases.ts @@ -0,0 +1,2 @@ +export * from './icons'; +export * from './aliases'; diff --git a/packages/lucide-icons/src/lucide-icons.prefixed.ts b/packages/lucide-icons/src/lucide-icons.prefixed.ts new file mode 100644 index 000000000..c916995f3 --- /dev/null +++ b/packages/lucide-icons/src/lucide-icons.prefixed.ts @@ -0,0 +1,3 @@ +export * as icons from './icons'; +export * from './aliases/prefixed'; +export * from './types'; diff --git a/packages/lucide-icons/src/lucide-icons.suffixed.ts b/packages/lucide-icons/src/lucide-icons.suffixed.ts new file mode 100644 index 000000000..551638884 --- /dev/null +++ b/packages/lucide-icons/src/lucide-icons.suffixed.ts @@ -0,0 +1,3 @@ +export * as icons from './icons'; +export * from './aliases/suffixed'; +export * from './types'; diff --git a/packages/lucide-icons/src/lucide-icons.ts b/packages/lucide-icons/src/lucide-icons.ts new file mode 100644 index 000000000..947ffb76e --- /dev/null +++ b/packages/lucide-icons/src/lucide-icons.ts @@ -0,0 +1,4 @@ +export * from './icons'; +export * as icons from './icons'; +export * from './aliases'; +export * from './types'; diff --git a/packages/lucide-icons/src/types.ts b/packages/lucide-icons/src/types.ts new file mode 100644 index 000000000..b4edfb1e3 --- /dev/null +++ b/packages/lucide-icons/src/types.ts @@ -0,0 +1,10 @@ +export type SVGProps = Record; + +export type LucideIconNode = [tag: string, attrs: SVGProps][]; + +export type LucideIconData = { + name: string; + node: LucideIconNode; +} & ({ size: number } | { width: number; height: number }); + +export type Icons = { [key: string]: LucideIconData }; diff --git a/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap new file mode 100644 index 000000000..4b1d355d0 --- /dev/null +++ b/packages/lucide-icons/tests/__snapshots__/createElement.spec.ts.snap @@ -0,0 +1,19 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`createElement > should match the snapshot 1`] = ` + + + + + + +`; diff --git a/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap new file mode 100644 index 000000000..16fd120aa --- /dev/null +++ b/packages/lucide-icons/tests/__snapshots__/lucide.spec.ts.snap @@ -0,0 +1,76 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`createIcons > should add custom attributes 1`] = ` + + + + + + + + +`; + +exports[`createIcons > should read elements from DOM and replace icon with alias name 1`] = ` + + + + + + + + + + + + +`; + +exports[`createIcons > should read elements from DOM and replace it with icons 1`] = ` + + + + + + + + +`; diff --git a/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap b/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap new file mode 100644 index 000000000..7a975d7b3 --- /dev/null +++ b/packages/lucide-icons/tests/__snapshots__/replaceElement.spec.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`combineClassNames > should returns a string of classNames 1`] = `"item item1 item2 item3 item4 item5 item6 item7 item8 item9"`; diff --git a/packages/lucide-icons/tests/createElement.spec.ts b/packages/lucide-icons/tests/createElement.spec.ts new file mode 100644 index 000000000..f4a896fd3 --- /dev/null +++ b/packages/lucide-icons/tests/createElement.spec.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { createElement, House } from '../src/lucide-icons'; +import { getOriginalSvg } from './helpers'; + +describe('createElement', () => { + it('should create SVG Element', () => { + const HomeSVG = createElement(House); + + expect(HomeSVG.tagName).toBe('svg'); + }); + + it('should match the snapshot', () => { + const HomeSVG = createElement(House); + + expect(HomeSVG.outerHTML).toMatchSnapshot(); + }); + + it('should create SVG Element with attributes', () => { + const HomeSVG = createElement(House, { fill: 'red' }); + + expect(HomeSVG.getAttribute('fill')).toBe('red'); + }); + + it('should create SVG Element with class name', () => { + const HomeSVG = createElement(House, { class: 'icon' }); + + expect(HomeSVG.getAttribute('class')).toBe('icon'); + }); + + it('should create the correct svg element', () => { + const HomeSVG = createElement(House); + + const svg = getOriginalSvg('house', undefined, false); + + expect(HomeSVG.outerHTML).toBe(svg); + }); +}); diff --git a/packages/lucide-icons/tests/helpers.ts b/packages/lucide-icons/tests/helpers.ts new file mode 100644 index 000000000..1e8e90fcd --- /dev/null +++ b/packages/lucide-icons/tests/helpers.ts @@ -0,0 +1,17 @@ +import fs from 'fs'; +import path from 'path'; +import { parseSync, stringify } from 'svgson'; + +const ICONS_DIR = path.resolve(__dirname, '../../../icons'); + +export const getOriginalSvg = (iconName: string, aliasName?: string, setAttrs = true) => { + const svgContent = fs.readFileSync(path.join(ICONS_DIR, `${iconName}.svg`), 'utf8'); + const svgParsed = parseSync(svgContent); + + if (setAttrs) { + svgParsed.attributes['data-lucide'] = aliasName ?? iconName; + svgParsed.attributes['class'] = `lucide lucide-${aliasName ?? iconName}`; + } + + return stringify(svgParsed, { selfClose: false }); +}; diff --git a/packages/lucide-icons/tests/lucide.spec.ts b/packages/lucide-icons/tests/lucide.spec.ts new file mode 100644 index 000000000..4cc231400 --- /dev/null +++ b/packages/lucide-icons/tests/lucide.spec.ts @@ -0,0 +1,91 @@ +import { describe, expect, it } from 'vitest'; +import { createIcons, icons } from '../src/lucide-icons'; +import { getOriginalSvg } from './helpers'; + +describe('createIcons', () => { + it('should read elements from DOM and replace it with icons', () => { + document.body.innerHTML = ``; + + createIcons({ icons }); + + const svg = getOriginalSvg('volume-2'); + + expect(document.body.innerHTML).toBe(svg); + expect(document.body.innerHTML).toMatchSnapshot(); + }); + + it('should customize the name attribute', () => { + document.body.innerHTML = ``; + + createIcons({ + icons, + nameAttr: 'data-custom-name', + }); + + const hasSvg = !!document.querySelector('svg'); + + expect(hasSvg).toBeTruthy(); + }); + + it('should add custom attributes', () => { + document.body.innerHTML = ``; + + const attrs = { + class: 'lucide lucide-volume-2 icon custom-class', + fill: 'black', + }; + + createIcons({ icons, attrs }); + + const element = document.querySelector('svg') as SVGSVGElement; + const attributes = element.getAttributeNames(); + + const attributesAndValues = attributes.reduce( + (acc, item) => { + acc[item] = element.getAttribute(item); + + return acc; + }, + {} as Record, + ); + + expect(document.body.innerHTML).toMatchSnapshot(); + + expect(attributesAndValues).toEqual(expect.objectContaining(attrs)); + }); + + it('should inherit elements attributes', () => { + document.body.innerHTML = ``; + + const attrs = { + 'data-theme-switcher': 'light', + }; + + createIcons({ icons }); + + const element = document.querySelector('svg') as SVGSVGElement; + const attributes = element.getAttributeNames(); + + const attributesAndValues = attributes.reduce( + (acc, item) => { + acc[item] = element.getAttribute(item); + + return acc; + }, + {} as Record, + ); + + expect(attributesAndValues).toEqual(expect.objectContaining(attrs)); + }); + + it('should read elements from DOM and replace icon with alias name', () => { + document.body.innerHTML = ``; + + createIcons({ icons }); + + const svg = getOriginalSvg('grid-3x3', 'grid'); + + expect(document.body.innerHTML).toBe(svg); + expect(document.body.innerHTML).toMatchSnapshot(); + }); +}); diff --git a/packages/lucide-icons/tests/replaceElement.spec.ts b/packages/lucide-icons/tests/replaceElement.spec.ts new file mode 100644 index 000000000..16d2742ab --- /dev/null +++ b/packages/lucide-icons/tests/replaceElement.spec.ts @@ -0,0 +1,64 @@ +import { describe, it, expect } from 'vitest'; +import { getAttrs, getClassNames, combineClassNames } from '../src/replaceElement'; + +describe('getAtts', () => { + it('should returns attrbrutes of an element', () => { + const element = { + attributes: [ + { + name: 'class', + value: 'item1 item2 item4', + }, + { + name: 'date-name', + value: 'volume', + }, + ], + }; + + const attrs = getAttrs(element as unknown as Element); + + expect(attrs.class).toBe(element.attributes[0].value); + }); +}); + +describe('getClassNames', () => { + it('should returns an array when giving class property of string', () => { + const elementAttrs = { + class: 'item1 item2 item3', + }; + + const attrs = getClassNames(elementAttrs); + expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3'])); + }); + + it('should returns an array when givind class property with an array', () => { + const elementAttrs = { + class: ['item1', 'item2', 'item3'], + }; + + const attrs = getClassNames(elementAttrs); + expect(JSON.stringify(attrs)).toBe(JSON.stringify(['item1', 'item2', 'item3'])); + }); +}); + +describe('combineClassNames', () => { + it('should returns a string of classNames', () => { + const arrayOfClassnames: (string | Record)[] = [ + 'item', + { + class: ['item1', 'item2', 'item3'], + }, + { + class: ['item4', 'item5', 'item6'], + }, + { + class: ['item7', 'item8', 'item9'], + }, + ]; + + const combinedClassNames = combineClassNames(arrayOfClassnames); + + expect(combinedClassNames).toMatchSnapshot(); + }); +}); diff --git a/packages/lucide-icons/tests/setupVitest.js b/packages/lucide-icons/tests/setupVitest.js new file mode 100644 index 000000000..03a0244af --- /dev/null +++ b/packages/lucide-icons/tests/setupVitest.js @@ -0,0 +1,5 @@ +import { expect } from 'vitest'; +import '@testing-library/jest-dom/vitest'; +import htmlSerializer from 'jest-serializer-html'; + +expect.addSnapshotSerializer(htmlSerializer); diff --git a/packages/lucide-icons/tsconfig.json b/packages/lucide-icons/tsconfig.json new file mode 100644 index 000000000..7fdaedbf6 --- /dev/null +++ b/packages/lucide-icons/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "declaration": true, + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "module": "ESNext", + "target": "ESNext", + "esModuleInterop": true, + "allowJs": true, + "lib": ["esnext", "dom"], + "resolveJsonModule": true, + "sourceMap": true, + "outDir": "./dist", + }, +} diff --git a/packages/lucide-icons/vitest.config.mts b/packages/lucide-icons/vitest.config.mts new file mode 100644 index 000000000..295f35b09 --- /dev/null +++ b/packages/lucide-icons/vitest.config.mts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'jsdom', + setupFiles: './tests/setupVitest.js', + } +});