diff --git a/.gitignore b/.gitignore index cf6cb25aa..9003ed236 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,9 @@ packages/**/src/aliases.ts !packages/**/src/aliases/index.ts packages/**/src/utils/*.ts !packages/shared/src/utils/*.ts +!packages/shared/src/build/ +!packages/shared/src/build/*.ts +packages/shared/src/build/types.d.ts packages/**/src/dynamicIconImports.ts packages/**/DynamicIcon.d.ts packages/**/dynamicIconImports.js diff --git a/docs/guide/angular/advanced/combining-icons.md b/docs/guide/angular/advanced/combining-icons.md index ec5c6a3b3..38b8a6199 100644 --- a/docs/guide/angular/advanced/combining-icons.md +++ b/docs/guide/angular/advanced/combining-icons.md @@ -2,6 +2,7 @@ title: Combining icons - Angular description: Learn how to combine multiple icons into a single icon nested SVG elements in your Angular application. --- + @@ -14,28 +15,32 @@ This is useful for if you want to be creative and make your own custom icons by ::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular"} ```ts /src/app/app.component.ts [active] -import { Component, ViewEncapsulation } from "@angular/core"; -import { LucideScan, LucideUser } from "@lucide/angular"; +import { Component, ViewEncapsulation } from '@angular/core'; +import { LucideScan, LucideUser } from '@lucide/angular'; @Component({ selector: 'app', imports: [LucideScan, LucideUser], - template: ` - - - `, + template: ` + + `, styleUrls: ['./app.component.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` + ::: This is valid SVG and all SVG properties are supported on the icons. @@ -53,28 +58,29 @@ You can also use SVG elements to create your own icons. For example, you can add a notification badge to an icon by using the `circle` SVG element. - ::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular"} ```ts /src/app/app.component.ts [active] -import { Component, ViewEncapsulation, signal } from "@angular/core"; -import { LucideMail } from "@lucide/angular"; +import { Component, ViewEncapsulation, signal } from '@angular/core'; +import { LucideMail } from '@lucide/angular'; @Component({ selector: 'app', imports: [LucideMail], - template: ` - - @if (hasUnreadMessages()) { - - } - `, + template: ` + @if (hasUnreadMessages()) { + + } + `, styleUrls: ['./app.component.css'], encapsulation: ViewEncapsulation.None, }) @@ -82,38 +88,40 @@ export class App { protected readonly hasUnreadMessages = signal(true); } ``` + ::: ### Example with text element You can also use the `text` SVG element to add text to your icon. - ::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular"} ```ts /src/app/app.component.ts [active] -import { Component, ViewEncapsulation } from "@angular/core"; -import { LucideFile } from "@lucide/angular"; +import { Component, ViewEncapsulation } from '@angular/core'; +import { LucideFile } from '@lucide/angular'; @Component({ selector: 'app', imports: [LucideFile], - template: ` - - - JS - - `, + template: ` + + JS + + `, styleUrls: ['./app.component.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` + ::: diff --git a/docs/guide/angular/advanced/global-styling.md b/docs/guide/angular/advanced/global-styling.md index d275dd8fa..3e275a8d9 100644 --- a/docs/guide/angular/advanced/global-styling.md +++ b/docs/guide/angular/advanced/global-styling.md @@ -2,6 +2,7 @@ title: Global Styling - Angular description: Learn how to style all icons globally in your Angular application using CSS or the provideLucideConfig provider. --- + @@ -29,9 +30,9 @@ import { provideLucideConfig } from '@lucide/angular'; export const appConfig: ApplicationConfig = { providers: [ provideLucideConfig({ - strokeWidth: 1.5 + strokeWidth: 1.5, }), - ] + ], }; ``` @@ -65,7 +66,7 @@ All Lucide icons include the `lucide` class. You can use this class in your styl ``` ```ts /src/app/app.component.ts -import { Component, ViewEncapsulation } from "@angular/core"; +import { Component, ViewEncapsulation } from '@angular/core'; import { LucideCakeSlice, LucideCandy, @@ -76,7 +77,7 @@ import { LucideSandwich, LucideWine, LucideDessert, -} from "@lucide/angular"; +} from '@lucide/angular'; @Component({ selector: 'app', @@ -92,28 +93,27 @@ import { LucideDessert, ], template: `
- - - - - - - - - -
`, + + + + + + + + + + `, styleUrls: ['./app.component.css', './icon.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` ::: -### Absolute stroke width +### Non-scaling strokes -To keep the stroke width constant regardless of icon size, apply `vector-effect: non-scaling-stroke` to the icon's children. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more details. +To keep the stroke width constant regardless of icon size, apply `vector-effect: non-scaling-stroke` to the icon's children. See [non-scaling strokes](../basics/stroke-width.md#non-scaling-strokes) for more details. ::: sandpack {template=angular editorHeight=300 dependencies="@lucide/angular"} @@ -137,7 +137,7 @@ To keep the stroke width constant regardless of icon size, apply `vector-effect: ``` ```ts /src/app/app.component.ts -import { Component, ViewEncapsulation } from "@angular/core"; +import { Component, ViewEncapsulation } from '@angular/core'; import { LucideTentTree, LucideCaravan, @@ -148,7 +148,7 @@ import { LucideMap, LucideCloudMoon, LucideSparkles, -} from "@lucide/angular"; +} from '@lucide/angular'; @Component({ selector: 'app', @@ -164,21 +164,20 @@ import { LucideSparkles, ], template: `
- - - - - - - - - -
`, + + + + + + + + + + `, styleUrls: ['./app.component.css', './icon.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` ::: diff --git a/docs/guide/angular/basics/stroke-width.md b/docs/guide/angular/basics/stroke-width.md index fab0df7ab..77327c0cb 100644 --- a/docs/guide/angular/basics/stroke-width.md +++ b/docs/guide/angular/basics/stroke-width.md @@ -1,7 +1,8 @@ --- title: Stroke width - Angular -description: Learn how to adjust the stroke width of icons in your Angular application using the `strokeWidth` input or adjust the strokeWidth appearance using the `absoluteStrokeWidth` input. +description: Learn how to adjust the stroke width of icons in your Angular application using the `strokeWidth` input or adjust the strokeWidth appearance using the `nonScalingStroke` input. --- + @@ -18,55 +19,58 @@ The `strokeWidth` can be adjusted to create a different look of the icons. ::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular"} ```ts /src/app/app.component.ts [active] -import { Component, ViewEncapsulation } from "@angular/core"; -import { LucideFolderLock } from "@lucide/angular"; +import { Component, ViewEncapsulation } from '@angular/core'; +import { LucideFolderLock } from '@lucide/angular'; @Component({ selector: 'app', imports: [LucideFolderLock], - template: ``, + template: ``, styleUrls: ['./app.component.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` + ::: -## Absolute stroke width +## Non-scaling strokes -When binding the `size` input, the icon's stroke width will be relative to its size, this is the default SVG behavior. The `absoluteStrokeWidth` input is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. +When binding the `size` input, the icon's stroke width will be relative to its size, this is the default SVG behavior. The `nonScalingStroke` input is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. -This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. +This means that when `nonScalingStroke` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes. -![Absolute stroke width comparison](../../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison") + -### Adjusting stroke width with `absoluteStrokeWidth` input +### Adjusting stroke width with `nonScalingStroke` input -Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute. +Setting `nonScalingStroke` to `true` will make the stroke width non-scaling. ::: sandpack {template=angular showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="@lucide/angular"} ```ts /src/app/app.component.ts [active] -import { Component, ViewEncapsulation } from "@angular/core"; -import { LucideRollerCoaster } from "@lucide/angular"; +import { Component, ViewEncapsulation } from '@angular/core'; +import { LucideRollerCoaster } from '@lucide/angular'; @Component({ selector: 'app', imports: [LucideRollerCoaster], template: ` - -`, + `, styleUrls: ['./app.component.css'], encapsulation: ViewEncapsulation.None, }) -export class App { -} +export class App {} ``` + ::: diff --git a/docs/guide/angular/getting-started.md b/docs/guide/angular/getting-started.md index bf45cb80e..805a5f841 100644 --- a/docs/guide/angular/getting-started.md +++ b/docs/guide/angular/getting-started.md @@ -2,6 +2,7 @@ title: Getting started - Angular description: This guide will help you get started with Lucide in your Angular project. --- + @@ -18,10 +19,13 @@ The `strokeWidth` can be adjusted to create a different look of the icons. ::: sandpack {template=vanilla showTabs=false editorHeight=250 editorWidthPercentage=70 dependencies="lucide"} ```html /index.html [active] - + - + @@ -29,34 +33,32 @@ The `strokeWidth` can be adjusted to create a different look of the icons. ``` ```js /index.js -import "./styles.css"; +import './styles.css'; import { createIcons, FolderLock } from 'lucide/dist/cjs/lucide'; createIcons({ icons: { FolderLock, - } + }, }); - ``` ::: + - -``` - -## Props - -You can pass additional props to adjust the icon appearance. - -| name | type | default | -|-----------------------|-----------|--------------| -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `strokeWidth` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | - -```html - -``` - -## Global configuration - -You can use `provideLucideConfig` to configure the default property values as defined above: - -```ts{2,7-9} -import { ApplicationConfig } from '@angular/core'; -import { provideLucideConfig } from '@lucide/angular'; - -export const appConfig: ApplicationConfig = { - providers: [ - // ... - provideLucideConfig({ - strokeWidth: 1.5 - }), - ] -}; -``` - -## Styling via CSS - -Icons can also be styled by using custom CSS classes: - -```html - -``` - -```css -svg.my-icon { - width: 12px; - height: 12px; - stroke-width: 3; -} -``` - -## With Lucide lab or custom icons - -[Lucide lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -While they aren't provided as standalone components, they can be still be passed to the `LucideIcon` component the same way as official icons: - -```html - - - - - -``` - -```ts{2,6-7,11-12} -import { provideLucideIcons } from '@lucide/angular'; -import { coconut } from '@lucide/lab'; - -@Component({ - templateUrl: './foobar.html', - // For using by name via provider: - providers: [provideLucideIcons({ coconut })], - imports: [LucideIcon] -}) -export class Foobar { - // For passing directly as LucideIconData: - readonly CoconutIcon = coconut; -} -``` - -## Troubleshooting - -### The icon is not being displayed - -If using per-icon-components: - -1. Ensure that the icon component is being imported, if using per-icon-components -2. Check that the icon name matches exactly (case-sensitive) - -If using the dynamic component: - -1. Ensure the icon is provided via `provideLucideIcons()` if using string names -2. Verify the icon is imported from `@lucide/angular` and not the legacy package - -### TypeScript errors? - -Make sure you're importing from `@lucide/angular` and not `lucide-angular`. - -### Icons render with wrong defaults - -Ensure `provideLucideConfig()` is used at the right level. - -## Migration guide - -Migrating from `lucide-angular`? Read our [comprehensive migration guide](https://github.com/lucide-icons/lucide/blob/main/packages/angular/MIGRATION.md). diff --git a/docs/guide/packages/lucide-astro.md b/docs/guide/packages/lucide-astro.md deleted file mode 100644 index 830f4f43a..000000000 --- a/docs/guide/packages/lucide-astro.md +++ /dev/null @@ -1,210 +0,0 @@ -# Lucide Astro - -Astro components for Lucide icons that work perfectly with Astro's island architecture and multi-framework support. Each icon is an Astro component that renders as an inline SVG, providing excellent performance for static sites and server-side rendering scenarios. - -**What you can accomplish:** -- Use icons as Astro components with zero JavaScript runtime overhead -- Build fast, static websites with optimized SVG icons -- Integrate seamlessly with Astro's component islands and partial hydration -- Create multi-framework applications where icons work across different UI libraries -- Optimize performance with direct icon imports and build-time rendering - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add @lucide/astro -``` - -```sh [yarn] -yarn add @lucide/astro -``` - -```sh [npm] -npm install @lucide/astro -``` - -```sh [bun] -bun add @lucide/astro -``` - -::: - -## How to use - -Lucide is built with ES Modules, so it's completely tree-shakable. - -Each icon can be imported as an Astro component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away. - -### Example - -Default usage: - -```astro ---- -import { Skull } from '@lucide/astro'; ---- - - -``` - -Additional props can be passed to adjust the icon: - -```astro ---- -import { Camera } from '@lucide/astro'; ---- - - -``` - -For faster builds and load times, you can import icons directly from the `@lucide/astro/icons` directory: - -```astro ---- -import CircleAlert from '@lucide/astro/icons/circle-alert'; ---- - - -``` - -## Props - -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | _number_ | 24 | -| `color` | _string_ | currentColor | -| `stroke-width` | _number_ | 2 | -| `absoluteStrokeWidth` | _boolean_ | false | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). - -```astro ---- -import { Phone } from '@lucide/astro'; ---- - - -``` - -This results a filled phone icon. - -## Types - -The package includes type definitions for all icons. This is useful if you want to dynamically render icons. - -### Example - -```astro ---- -import { House, Library, Cog, type Icon as IconType } from '@lucide/astro'; - -type MenuItem = { - name: string; - href: string; - icon: typeof IconType; -}; - -const menuItems: MenuItem[] = [ - { - name: 'Home', - href: '/', - icon: House, - }, - { - name: 'Blog', - href: '/blog', - icon: Library, - }, - { - name: 'Projects', - href: '/projects', - icon: Cog, - }, -]; ---- - -{ - menuItems.map((item) => ( - - - {item.name} - - )) -} -``` - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props of the regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```astro ---- -import { Icon } from '@lucide/astro'; -import { burger, sausage } from '@lucide/lab'; ---- - - - -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons, but it is not recommended. - -::: danger -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application. This may be passable if you're doing SSG and SSR in server environments. However if you're doing SSR in serverless environments, it could negatively affect your app's performance, as a bigger bundle size will translate to an increase in cold starts. -::: - -### Icon Component Example - -```astro ---- -import { icons, type IconProps } from '@lucide/astro'; - -interface Props extends IconProps { - name: keyof typeof icons; -} - -const { name, ...restProps } = Astro.props; -const Icon = icons[name]; ---- - - -``` - -### Using the Icon Component - -```astro ---- -import LucideIcon from './LucideIcon.astro'; ---- - - -``` - -## Accessibility - -By default, we hide icons from screen readers using `aria-hidden="true"`. - -You can add accessibility attributes using aria-labels. - -```jsx ---- -import { Check } from '@lucide/astro'; ---- - - -``` - -For best practices on accessibility, please see our [accessibility guide](../accessibility.md). diff --git a/docs/guide/packages/lucide-preact.md b/docs/guide/packages/lucide-preact.md deleted file mode 100644 index 1fe1bf1d4..000000000 --- a/docs/guide/packages/lucide-preact.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Lucide Preact ---- - -# Lucide Preact - -Preact components for Lucide icons that provide React-like development experience with a smaller footprint. Each icon is a lightweight Preact component that renders as an inline SVG, perfect for applications that need React compatibility with minimal bundle size. - -**What you can accomplish:** -- Use icons as Preact components with React-like syntax and patterns -- Build lightweight applications with Preact's smaller runtime -- Create fast, responsive interfaces with minimal JavaScript overhead -- Maintain React compatibility while reducing bundle size -- Integrate with existing Preact applications and component libraries - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add lucide-preact -``` - -```sh [yarn] -yarn add lucide-preact -``` - -```sh [npm] -npm install lucide-preact -``` - -```sh [bun] -bun add lucide-preact -``` - -::: - -## How to use - -Lucide is built with ES Modules, so it's completely tree-shakable. - -Each icon can be imported as a Preact component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away. - -### Example - -Additional props can be passed to adjust the icon: - -```jsx -import { Camera } from 'lucide-preact'; - -// Usage -const App = () => { - return ; -}; - -export default App; -``` - -## Props - -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `strokeWidth` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). - -```jsx -// Usage -const App = () => { - return ; -}; -``` - -> SVG attributes in Preact aren't transformed, so if you want to change for example the `stroke-linejoin` you need to pass it in kebabcase. Basically how the SVG spec want you to write it. See this topic in the [Preact documentation](https://preactjs.com/guide/v10/differences-to-react/#svg-inside-jsx). - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props like regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```jsx -import { Icon } from 'lucide-preact'; -import { coconut } from '@lucide/lab'; - -const App = () => ( - -); -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons, but it is not recommended. - -::: danger -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`. -::: - -### Icon Component Example - -```jsx -import { icons } from 'lucide-preact'; - -const Icon = ({ name, color, size }) => { - const LucideIcon = icons[name]; - - return ; -}; - -export default Icon; -``` - -#### Using the Icon Component - -```jsx -import Icon from './Icon'; - -const App = () => { - return ; -}; - -export default App; -``` - -## Accessibility - -By default, we hide icons from screen readers using `aria-hidden="true"`. - -You can add accessibility attributes using aria-labels. - -```jsx -import { Check } from 'lucide-preact'; - -const App = () => { - return ; -}; -``` - -For best practices on accessibility, please see our [accessibility guide](../accessibility.md). diff --git a/docs/guide/packages/lucide-react-native.md b/docs/guide/packages/lucide-react-native.md deleted file mode 100644 index 6d148ce76..000000000 --- a/docs/guide/packages/lucide-react-native.md +++ /dev/null @@ -1,133 +0,0 @@ -# Lucide React Native - -React Native components for Lucide icons that work seamlessly across iOS and Android platforms. Built on top of react-native-svg, each icon renders as a native SVG component, providing consistent visual appearance and performance across mobile devices. - -**What you can accomplish:** -- Use icons as React Native components with platform-consistent rendering -- Build cross-platform mobile apps with scalable vector icons -- Create responsive interfaces that adapt to different screen densities -- Integrate with React Native's styling system and animation libraries -- Maintain consistent icon appearance across iOS and Android platforms - -## Installation - -First, ensure that you have `react-native-svg` (version between 12 and 15) installed. Then, install the package: - -::: code-group - -```sh [pnpm] -pnpm add lucide-react-native -``` - -```sh [yarn] -yarn add lucide-react-native -``` - -```sh [npm] -npm install lucide-react-native -``` - -```sh [bun] -bun add lucide-react-native -``` - -::: - -## How to use - -Each icon can be imported as a React component. - -### Example - -Additional props can be passed to adjust the icon: - -```jsx -import { Camera } from 'lucide-react-native'; - -// Usage -const App = () => { - return ; -}; - -export default App; -``` - -## Props - -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `strokeWidth` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. - -```jsx -// Usage -const App = () => { - return ; -}; -``` - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props like regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```jsx -import { Icon } from 'lucide-react-native'; -import { coconut } from '@lucide/lab'; - -const App = () => ( - -); -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons, but it is not recommended. - -::: warning -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important to keep in mind when using bundlers like `Webpack`, `Rollup`, or `Vite`. -::: - -### Icon Component Example - -```tsx -import * as icons from 'lucide-react-native/icons'; - -interface IconProps { - name: keyof typeof icons; - color?: string; - size?: number; -} - -const Icon = ({ name, color, size }: IconProps) => { - const LucideIcon = icons[name]; - - return ; -}; - -export default Icon; -``` - -#### Using the Icon Component - -```tsx -import Icon from './Icon'; - -const App = () => { - return ; -}; - -export default App; -``` diff --git a/docs/guide/packages/lucide-solid.md b/docs/guide/packages/lucide-solid.md deleted file mode 100644 index 63157ff4f..000000000 --- a/docs/guide/packages/lucide-solid.md +++ /dev/null @@ -1,162 +0,0 @@ -# Lucide Solid - -SolidJS components for Lucide icons that leverage Solid's fine-grained reactivity system. Each icon is a reactive Solid component that renders as an inline SVG, providing exceptional performance through Solid's compile-time optimizations and reactive primitives. - -**What you can accomplish:** -- Use icons as SolidJS components with fine-grained reactivity -- Create highly performant interfaces with Solid's reactive system -- Build dynamic icon components that respond to signals and stores -- Integrate seamlessly with Solid's JSX and component patterns -- Optimize performance with direct icon imports and minimal runtime overhead - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add lucide-solid -``` - -```sh [yarn] -yarn add lucide-solid -``` - -```sh [npm] -npm install lucide-solid -``` - -```sh [bun] -bun add lucide-solid -``` - -::: - -## How to use - -Lucide is built with ES Modules, so it's completely tree-shakable. - -Each icon can be imported as a Solid component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away. - -### Example - -Additional props can be passed to adjust the icon: - -```jsx -import { Camera } from 'lucide-solid'; - -// Usage -const App = () => { - return ; -}; - -export default App; -``` - -Vite loading/performing issues with the dev server can be resolved by import icons directly from the `lucide-solid/icons` directory: - -```jsx -import Camera from 'lucide-solid/icons/camera'; - -// Usage -const App = () => { - return ; -}; - -export default App; -``` - -## Props - -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `strokeWidth` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). - -```jsx -// Usage -const App = () => { - return ; -}; -``` - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props like the regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```jsx -import { Icon } from 'lucide-solid'; -import { sausage } from '@lucide/lab'; - -const App = () => ( - -); -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons. It's not recommended. - -::: danger -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important to keep in mind when using bundlers like `Webpack`, `Rollup`, or `Vite`. -::: - -### Icon Component Example - -```tsx -import { icons, type LucideProps } from 'lucide-solid'; -import { splitProps } from 'solid-js'; -import { Dynamic } from 'solid-js/web'; - -interface IconProps extends LucideProps { - name: keyof typeof icons; -} - -const Icon = (props: IconProps) => { - const [local, others] = splitProps(props, ["name"]); - - return -}; - -export default Icon; -``` - -#### Using the Icon Component - -```tsx -import Icon from './Icon'; - -const App = () => { - return ; -}; - -export default App; -``` - -## Accessibility - -By default, we hide icons from screen readers using `aria-hidden="true"`. - -You can add accessibility attributes using aria-labels. - -```jsx -import { Check } from 'lucide-solid'; - -const App = () => { - return ; -}; -``` - -For best practices on accessibility, please see our [accessibility guide](../accessibility.md). diff --git a/docs/guide/packages/lucide-static.md b/docs/guide/packages/lucide-static.md deleted file mode 100644 index 22e1d976d..000000000 --- a/docs/guide/packages/lucide-static.md +++ /dev/null @@ -1,265 +0,0 @@ -# Lucide Static - -Static assets and utilities for Lucide icons that work without JavaScript frameworks. This package provides multiple formats including individual SVG files, SVG sprites, icon fonts, and Node.js utilities for server-side rendering and static site generation. - -**What you can accomplish:** -- Use individual SVG files as images or CSS background images -- Implement icon fonts for CSS-based icon systems -- Create SVG sprites for efficient icon loading in static sites -- Import SVG strings in Node.js applications and server-side rendering -- Build static websites and applications without JavaScript framework dependencies - -This package includes the following implementations of Lucide icons: - -- Individual SVG files -- SVG sprite -- Icon font files -- A JavaScript library exporting SVG strings - -## Who is this for? - -`lucide-static` is suitable for _very specific use cases_ where you want to use Lucide icons without relying on a JavaScript framework or component system. It's ideal for: - -- Projects that use icon fonts with plain CSS or utility-first frameworks -- Embedding raw SVG files or sprites directly in HTML -- Using SVGs as CSS background images -- Importing SVG strings into Node.js (CommonJS) environments - -::: danger -### Not recommended for production {#production-warning} - -SVG sprites and icon fonts include **all icons**, which can significantly increase your app's bundle size and load time. - -For production environments, we recommend using a bundler with tree-shaking support to include only the icons you actually use. Consider using one of the framework-specific [packages](../../packages.md). -::: - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add lucide-static -``` - -```sh [yarn] -yarn add lucide-static -``` - -```sh [npm] -npm install lucide-static -``` - -```sh [bun] -bun add lucide-static -``` - -::: - -## SVG Files - -You can use standalone SVG files or SVG sprites in several ways. - -Check out our [codesandbox example](https://codesandbox.io/s/using-the-svg-sprite-lz1kk). - -### SVG file as image - -#### In HTML: - -::: code-group - -```html [Webpack] - - -``` - -```html [CDN] - - -``` - -::: - -#### In CSS: - -::: code-group - -```css [Webpack] -.house-icon { - background-image: url(~lucide-static/icons/house.svg); -} -``` - -```css [CDN] -.house-icon { - background-image: url(https://unpkg.com/lucide-static@latest/icons/house.svg); -} -``` - -::: - -Make sure you have the correct Webpack loader configured, such as [`url-loader`](https://v4.webpack.js.org/loaders/url-loader/). - -### SVG file as string - -To import an SVG as a string (e.g., for templating): - -::: code-group - -```js [Webpack] -import arrowRightIcon from 'lucide-static/icons/arrow-right'; -``` - -```js [Vite] -import arrowRightIcon from 'lucide-static/icons/arrow-right.svg?raw'; -``` - -::: - -You'll need an SVG loader like [`svg-inline-loader`](https://v4.webpack.js.org/loaders/svg-inline-loader/). - - -### Using the SVG sprite - -:::danger -[Not intended for production use.](#production-warning) -::: - -You may also need an additional SVG loader to handle this. - -#### Basic sprite usage (not production-optimized): - -```html - -``` - -#### Inline usage: - -```html - - - - - -... -``` - -#### Inline with CSS helper class - -If you'd prefer, you can use CSS to hold your base SVG properties: - -```css -.lucide-icon { - width: 24px; - height: 24px; - stroke: currentColor; - fill: none; - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; -} -``` - -...and update the SVG as follows: - -```xml - - - - - -... -``` - -## Icon font - -:::danger -[Not intended for production use.](#production-warning) -::: - -Lucide icons are also available as a web font. To use them, you first need to include the corresponding stylesheet: - -::: code-group - -```css [Vite] -@import 'lucide-static/font/lucide.css'; -``` - -```css [Webpack] -@import ('~lucide-static/font/lucide.css'); -``` - -```html [CDN] - -``` - -```html [Static asset] - -``` - -::: - -Once included, use the format `icon-{kebab-case-name}`. You can copy icon names from the [Lucide Icons page](https://lucide.dev/icons). - -```html -
-``` - -If you're not using a package manager, you can download the font files directly from the [latest release](https://github.com/lucide-icons/lucide/releases/latest). - -## Node.js - -You can also import Lucide icons in Node.js projects: - -::: code-group - -```js [ESM] -import {MessageSquare} from 'lucide-static'; -``` - -```js [CommonJs] -const {MessageSquare} = require('lucide-static'); -``` - -::: - -> Note: Each icon name is in PascalCase. - -#### Express app example in Node.js - -```js -import express from 'express'; -import {MessageSquare} from 'lucide-static'; -const app = express(); -const port = 3000; - -app.get('/', (req, res) => { - res.send(` - - - - Page Title - - -

Lucide Icons

-

This is a Lucide icon ${MessageSquare}

- - - - `); -}); - -app.listen(port, () => { - console.log(`Example app listening at http://localhost:${port}`); -}); -``` diff --git a/docs/guide/packages/lucide-svelte.md b/docs/guide/packages/lucide-svelte.md deleted file mode 100644 index d46582825..000000000 --- a/docs/guide/packages/lucide-svelte.md +++ /dev/null @@ -1,349 +0,0 @@ -# Lucide Svelte - -Svelte components for Lucide icons that work seamlessly with both Svelte 4 and Svelte 5. Each icon is a reactive Svelte component that renders as an inline SVG, providing excellent performance and integration with Svelte's reactive system and modern features. - -**What you can accomplish:** -- Use icons as Svelte components with full reactivity and TypeScript support -- Bind icon properties to reactive variables and stores -- Create dynamic icon systems that respond to application state -- Build type-safe interfaces with comprehensive TypeScript definitions -- Optimize bundle sizes with direct icon imports and tree-shaking - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add @lucide/svelte -``` - -```sh [yarn] -yarn add @lucide/svelte -``` - -```sh [npm] -npm install @lucide/svelte -``` - -```sh [bun] -bun add @lucide/svelte -``` -::: -> `@lucide/svelte` is only for Svelte 5, for Svelte 4 use the `lucide-svelte` package. - -## How to use - -Lucide is built with ES Modules, so it's completely tree-shakable. - -Each icon can be imported as a Svelte component, which renders an inline SVG element. This way, only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away. - -### Example - -Default usage: - -```svelte - - - -``` - -Additional props can be passed to adjust the icon: - -```svelte - - - -``` - -For faster builds and load times, you can import icons directly from the `@lucide/svelte/icons` directory: - -```svelte - - - -``` - -## Props - -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | _number_ | 24 | -| `color` | _string_ | currentColor | -| `strokeWidth` | _number_ | 2 | -| `absoluteStrokeWidth` | _boolean_ | false | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). - -```svelte - - - -``` - -This results a filled phone icon. - -## Types - -The package includes type definitions for all icons. This is useful if you want to dynamically load icons with the `svelte:component` directive whether you are using TypeScript or JSDoc. - -### TypeScript Example - -::: code-group - -```svelte [Svelte 5] - - -{#each menuItems as item} - {@const Icon = item.icon} - - - {item.name} - -{/each} -``` - -```svelte [Svelte 4] - - -{#each menuItems as item} - {@const Icon = item.icon} - - - {item.name} - -{/each} - -``` -::: - -### JSDoc Example - -::: code-group - -```svelte [Svelte 5] - - -{#each menuItems as item} - {@const Icon = item.icon} - - - {item.name} - -{/each} -``` - -```svelte [Svelte 4] - - -{#each menuItems as item} - {@const Icon = item.icon} - - - {item.name} - -{/each} -``` - -::: - -For more details about typing the `svelte:component` directive, see the [Svelte documentation](https://svelte.dev/docs/typescript#types-componenttype). - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props like the regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```svelte - - - - -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons, but it is not recommended. - -::: danger -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`. -::: - -### Icon Component Example - -::: code-group - -```svelte [Svelte 5] - - - -``` - -```svelte [Svelte 4] - - - -``` - -::: - -#### Using the Icon Component - -```svelte - - - -``` - -## Accessibility - -By default, we hide icons from screen readers using `aria-hidden="true"`. - -You can add accessibility attributes using aria-labels. - -```svelte - - - -``` - -For best practices on accessibility, please see our [accessibility guide](../accessibility.md). diff --git a/docs/guide/packages/lucide-vue.md b/docs/guide/packages/lucide-vue.md deleted file mode 100644 index f5ac0ff10..000000000 --- a/docs/guide/packages/lucide-vue.md +++ /dev/null @@ -1,171 +0,0 @@ -# Lucide Vue - -Vue 2 components for Lucide icons that integrate with Vue's Options API and template system. Each icon is a Vue component that renders as an inline SVG, providing familiar Vue development patterns for legacy applications still using Vue 2. - -**What you can accomplish:** - -- Use icons as Vue 2 components with Options API integration -- Maintain legacy Vue 2 applications with modern icon components -- Integrate with Vue 2's template system and component lifecycle -- Build applications using Vue 2's familiar syntax and patterns -- Bridge the gap while planning migration to Vue 3 - -## Installation - -::: code-group - -```sh [pnpm] -pnpm add @lucide/vue -``` - -```sh [yarn] -yarn add @lucide/vue -``` - -```sh [npm] -npm install @lucide/vue -``` - -```sh [bun] -bun add @lucide/vue -``` - -::: - -## How to use - -Lucide is built with ES Modules, so it's completely tree-shakable. - -Each icon can be imported as a Vue component, which renders an inline SVG Element. This way only the icons that are imported into your project are included in the final bundle. The rest of the icons are tree-shaken away. - -### Example - -You can pass additional props to adjust the icon. - -```vue - - - -``` - -## Props - -| name | type | default | -| ----------------------- | --------- | ------------ | -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `stroke-width` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | -| `default-class` | *string* | lucide-icon | - -### Applying props - -To customize the appearance of an icon, you can pass custom properties as props directly to the component. The component accepts all SVG attributes as props, which allows flexible styling of the SVG elements. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). - -```vue - -``` - -## With Lucide Lab or custom icons - -[Lucide Lab](https://github.com/lucide-icons/lucide-lab) is a collection of icons that are not part of the Lucide main library. - -They can be used by using the `Icon` component. -All props like regular Lucide icons can be passed to adjust the icon appearance. - -### Using the `Icon` component - -This creates a single icon based on the iconNode passed and renders a Lucide icon component. - -```vue - - - -``` - -## One generic icon component - -It is possible to create one generic icon component to load icons, but it is not recommended. - -::: danger -The example below imports all ES Modules, so exercise caution when using it. Importing all icons will significantly increase the build size of the application, negatively affecting its performance. This is especially important when using bundlers like `Webpack`, `Rollup`, or `Vite`. -::: - -### Icon Component Example - -```vue - - - -``` - -### Using the Icon Component - -All other props listed above also work on the `Icon` Component. - -```vue - -``` - -## Accessibility - -By default, we hide icons from screen readers using `aria-hidden="true"`. - -You can add accessibility attributes using aria-labels. - -```vue - - - -``` - -For best practices on accessibility, please see our [accessibility guide](../accessibility.md). diff --git a/docs/guide/preact/advanced/combining-icons.md b/docs/guide/preact/advanced/combining-icons.md index 8f5ffd92f..b8bcf7c6f 100644 --- a/docs/guide/preact/advanced/combining-icons.md +++ b/docs/guide/preact/advanced/combining-icons.md @@ -2,6 +2,7 @@ title: Combining icons - Preact description: Learn how to combine multiple icons into a single icon nested SVG elements in your Preact application. --- + @@ -14,18 +15,21 @@ This is useful if you want to create custom icons by combining existing ones. ::: sandpack {showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="lucide-preact"} ```jsx App.js [active] -import { Scan, User } from "lucide-preact"; -import { h } from "preact"; +import { Scan, User } from 'lucide-preact'; +import { h } from 'preact'; function App() { return (
- +
@@ -33,8 +37,8 @@ function App() { } export default App; - ``` + ::: This is valid, since [SVGs can be nested](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/svg#nested_svg_element), and all SVG properties are supported on the icons. @@ -55,8 +59,8 @@ For example, you can add a notification badge to an icon by using the `circle` S ::: sandpack {showTabs=false editorHeight=580 editorWidthPercentage=60 dependencies="lucide-preact"} ```jsx App.js [active] -import { Mail } from "lucide-preact"; -import { h } from "preact"; +import { Mail } from 'lucide-preact'; +import { h } from 'preact'; function App() { const hasUnreadMessages = true; @@ -79,8 +83,8 @@ function App() { } export default App; - ``` + ::: ### Example with text element @@ -90,8 +94,8 @@ You can also use the `text` SVG element to add text to your icon. ::: sandpack {showTabs=false editorHeight=400 editorWidthPercentage=60 dependencies="lucide-preact"} ```jsx App.js [active] -import { File } from "lucide-preact"; -import { h } from "preact"; +import { File } from 'lucide-preact'; +import { h } from 'preact'; function App() { return ( @@ -112,6 +116,6 @@ function App() { } export default App; - ``` + ::: diff --git a/docs/guide/preact/advanced/global-styling.md b/docs/guide/preact/advanced/global-styling.md index c8e77feb7..47b24d7fd 100644 --- a/docs/guide/preact/advanced/global-styling.md +++ b/docs/guide/preact/advanced/global-styling.md @@ -2,6 +2,7 @@ title: Global Styling - Preact description: Learn how to style all Lucide icons globally in your Preact application using CSS or the Lucide context provider. --- + @@ -34,8 +35,8 @@ const App = () => ( This will apply the `color`, `size` and `strokeWidth` props to all icons that are children of the `LucideProvider`. - ## Style by using CSS + Styling icons is easy to accomplish using CSS. Every icon has a class attribute applied called `lucide`. This class name can be used in the CSS file to target all icons that are being used within the app. @@ -74,9 +75,9 @@ import { Sandwich, Wine, Dessert, -} from "lucide-preact"; -import { h } from "preact"; -import "./icon.css"; +} from 'lucide-preact'; +import { h } from 'preact'; +import './icon.css'; function App() { return ( @@ -99,10 +100,9 @@ export default App; ::: +### Non-scaling strokes -### Absolute stroke width - -For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info. +For global non-scaling stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [non-scaling strokes](../basics/stroke-width.md#non-scaling-strokes) for more info. ::: sandpack {editorHeight=480 editorWidthPercentage=60 dependencies="lucide-preact"} @@ -136,9 +136,9 @@ import { Map, CloudMoon, Sparkles, -} from "lucide-preact"; -import { h } from "preact"; -import "./icon.css"; +} from 'lucide-preact'; +import { h } from 'preact'; +import './icon.css'; function App() { return ( diff --git a/docs/guide/preact/advanced/typescript.md b/docs/guide/preact/advanced/typescript.md index b55c7c720..2d8dd926d 100644 --- a/docs/guide/preact/advanced/typescript.md +++ b/docs/guide/preact/advanced/typescript.md @@ -2,6 +2,7 @@ title: Typescript - Preact description: Learn about the different types exported by the `lucide-preact` package and how to use them in your Preact application. --- + # TypeScript List of exported types from the `lucide-preact` package. @@ -16,12 +17,17 @@ interface LucideProps { size?: number | string; color?: string; strokeWidth?: number; + nonScalingStroke?: boolean; + /** + * @deprecated + */ absoluteStrokeWidth?: boolean; [key: string]: any; // Any other SVG attributes } ``` ### Using `LucideProps` + You can use the `LucideProps` interface to type your custom icon components or when you need to work with icon props. ```tsx @@ -76,6 +82,7 @@ type IconNode = [elementName: string, attrs: Record][]; ``` ### Using `IconNode` + You can use the `IconNode` type when you need to work with the raw SVG structure of an icon. ```tsx @@ -89,7 +96,11 @@ const customIcon: IconNode = [ const MyCustomIcon = () => { return ( - + ); }; diff --git a/docs/guide/preact/basics/stroke-width.md b/docs/guide/preact/basics/stroke-width.md index fe88199b3..b2c9d9e60 100644 --- a/docs/guide/preact/basics/stroke-width.md +++ b/docs/guide/preact/basics/stroke-width.md @@ -1,7 +1,8 @@ --- title: Stroke width - Preact -description: Learn how to adjust the stroke width of icons in your Preact application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `absoluteStrokeWidth` prop. +description: Learn how to adjust the stroke width of icons in your Preact application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `nonScalingStroke` prop. --- + @@ -18,8 +19,8 @@ The `strokeWidth` can be adjusted to create a different look of the icons. ::: sandpack {showTabs=false editorHeight=320 editorWidthPercentage=60 dependencies="lucide-preact"} ```jsx App.js [active] -import { h } from "preact"; -import { FolderLock } from "lucide-preact"; +import { h } from 'preact'; +import { FolderLock } from 'lucide-preact'; function App() { return ( @@ -31,34 +32,35 @@ function App() { export default App; ``` + ::: -## Absolute stroke width +## Non-scaling strokes -When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `absoluteStrokeWidth` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. +When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `nonScalingStroke` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. -This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. +This means that when `nonScalingStroke` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes. -![Absolute stroke width comparison](../../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison") + -### Adjusting stroke width with `absoluteStrokeWidth` prop +### Adjusting stroke width with `nonScalingStroke` prop -Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute. +Setting `nonScalingStroke` to `true` will make the stroke width non-scaling. ::: sandpack {showTabs=false editorHeight=340 editorWidthPercentage=60 dependencies="lucide-preact"} ```jsx App.js [active] -import { h } from "preact"; -import { RollerCoaster } from "lucide-preact"; +import { h } from 'preact'; +import { RollerCoaster } from 'lucide-preact'; function App() { return (
); @@ -66,4 +68,5 @@ function App() { export default App; ``` + ::: diff --git a/docs/guide/preact/getting-started.md b/docs/guide/preact/getting-started.md index f7343b9fe..71babbd79 100644 --- a/docs/guide/preact/getting-started.md +++ b/docs/guide/preact/getting-started.md @@ -2,6 +2,7 @@ title: Getting started - Preact description: This guide will help you get started with Lucide in your Preact project. --- + @@ -36,8 +37,8 @@ const App = () => ( This will apply the `color`, `size` and `strokeWidth` props to all icons that are children of the `LucideProvider`. - ## Style by using CSS + Styling icons is easy to accomplish using CSS. Every icon has a class attribute applied called `lucide`. This class name can be used in the CSS file to target all icons that are being used within the app. @@ -75,7 +76,7 @@ import IceCream2 from 'lucide-solid/icons/ice-cream-2'; import Sandwich from 'lucide-solid/icons/sandwich'; import Wine from 'lucide-solid/icons/wine'; import Dessert from 'lucide-solid/icons/dessert'; -import "./icon.css"; +import './icon.css'; function App() { return ( @@ -95,11 +96,12 @@ function App() { export default App; ``` + ::: -### Absolute stroke width +### Non-scaling strokes -For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info. +For global non-scaling stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [non-scaling strokes](../basics/stroke-width.md#non-scaling-strokes) for more info. ::: sandpack {template=vite-solid editorHeight=300 editorWidthPercentage=60 dependencies="lucide-solid"} @@ -120,7 +122,6 @@ For global absolute stroke width styling the `vector-effect: non-scaling-stroke` grid-template-rows: 1fr 1fr 1fr; gap: 6px; } - ``` ```tsx App.tsx @@ -133,7 +134,7 @@ import Axe from 'lucide-solid/icons/axe'; import Map from 'lucide-solid/icons/map'; import CloudMoon from 'lucide-solid/icons/cloud-moon'; import Sparkles from 'lucide-solid/icons/sparkles'; -import "./icon.css"; +import './icon.css'; function App() { return ( diff --git a/docs/guide/solid/advanced/typescript.md b/docs/guide/solid/advanced/typescript.md index 1142e3b0f..a5f4f0026 100644 --- a/docs/guide/solid/advanced/typescript.md +++ b/docs/guide/solid/advanced/typescript.md @@ -2,6 +2,7 @@ title: Typescript - Solid description: Learn about the different types exported by the `lucide-solid` package and how to use them in your Solid application. --- + # TypeScript Support List of exported types from the `lucide-solid` package. @@ -16,12 +17,17 @@ interface LucideProps extends SVGAttributes { size?: number | string; color?: string; strokeWidth?: number; + nonScalingStroke?: boolean; + /** + * @deprecated + */ absoluteStrokeWidth?: boolean; [key: string]: any; // Any other SVG attributes } ``` ### Using `LucideProps` + You can use the `LucideProps` interface to type your custom icon components or when you need to work with icon props. ```tsx @@ -76,6 +82,7 @@ type IconNode = [elementName: string, attrs: Record][]; ``` ### Using `IconNode` + You can use the `IconNode` type when you need to work with the raw SVG structure of an icon. ```tsx @@ -89,7 +96,11 @@ const customIcon: IconNode = [ const MyCustomIcon = () => { return ( - + ); }; diff --git a/docs/guide/solid/basics/stroke-width.md b/docs/guide/solid/basics/stroke-width.md index 8e4e84f96..5b7ff8c47 100644 --- a/docs/guide/solid/basics/stroke-width.md +++ b/docs/guide/solid/basics/stroke-width.md @@ -1,7 +1,8 @@ --- title: Stroke width - Solid -description: Learn how to adjust the stroke width of icons in your Solid application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `absoluteStrokeWidth` prop. +description: Learn how to adjust the stroke width of icons in your Solid application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `nonScalingStroke` prop. --- + @@ -30,21 +31,22 @@ function App() { export default App; ``` + ::: -## Absolute stroke width +## Non-scaling strokes -When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `absoluteStrokeWidth` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. +When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `nonScalingStroke` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. -This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. +This means that when `nonScalingStroke` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes. -![Absolute stroke width comparison](../../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison") + -### Adjusting stroke width with `absoluteStrokeWidth` prop +### Adjusting stroke width with `nonScalingStroke` prop -Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute. +Setting `nonScalingStroke` to `true` will make the stroke width non-scaling. ::: sandpack {template=vite-solid showTabs=false editorHeight=320 editorWidthPercentage=60 dependencies="lucide-solid"} @@ -56,7 +58,7 @@ function App() {
); @@ -64,4 +66,5 @@ function App() { export default App; ``` + ::: diff --git a/docs/guide/solid/getting-started.md b/docs/guide/solid/getting-started.md index b058ad4d5..68edf02f2 100644 --- a/docs/guide/solid/getting-started.md +++ b/docs/guide/solid/getting-started.md @@ -57,19 +57,25 @@ export default App; To customize the appearance of an icon, you can use the following props: -| name | type | default | -| --------------------- | --------- | ------------ | -| `size` | *number* | 24 | -| `color` | *string* | currentColor | -| `strokeWidth` | *number* | 2 | -| `absoluteStrokeWidth` | *boolean* | false | +| name | type | default | +| ------------------ | --------- | ------------ | +| `size` | _number_ | 24 | +| `color` | _string_ | currentColor | +| `strokeWidth` | _number_ | 2 | +| `nonScalingStroke` | _boolean_ | false | Because icons render as SVG elements, all standard SVG attributes can also be applied as props. See the list of SVG Presentation Attributes on [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/Presentation). ```jsx // Usage const App = () => { - return ; + return ( + + ); }; ``` diff --git a/docs/guide/svelte/advanced/combining-icons.md b/docs/guide/svelte/advanced/combining-icons.md index dd9c30df8..8a267a5a9 100644 --- a/docs/guide/svelte/advanced/combining-icons.md +++ b/docs/guide/svelte/advanced/combining-icons.md @@ -2,6 +2,7 @@ title: Combining icons - Svelte description: Learn how to combine multiple icons into a single icon nested SVG elements in your Svelte application. --- + @@ -20,12 +21,12 @@ import User from '@lucide/svelte/icons/user';
- +
diff --git a/docs/guide/svelte/advanced/global-styling.md b/docs/guide/svelte/advanced/global-styling.md index 42f1ea061..d96cc302f 100644 --- a/docs/guide/svelte/advanced/global-styling.md +++ b/docs/guide/svelte/advanced/global-styling.md @@ -2,6 +2,7 @@ title: Global Styling - Svelte description: Learn how to style all Lucide icons globally in your Svelte application using CSS or the Lucide context provider. --- + @@ -89,11 +90,12 @@ import "./icon.css"; ``` + ::: -### Absolute stroke width +### Non-scaling strokes -For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info. +For global non-scaling stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [non-scaling strokes](../basics/stroke-width.md#non-scaling-strokes) for more info. ::: sandpack {template=vite-svelte editorHeight=420 editorWidthPercentage=55} @@ -143,4 +145,5 @@ import "./icon.css"; ``` + ::: diff --git a/docs/guide/svelte/advanced/typescript.md b/docs/guide/svelte/advanced/typescript.md index 08aab6f0f..ce3445fc9 100644 --- a/docs/guide/svelte/advanced/typescript.md +++ b/docs/guide/svelte/advanced/typescript.md @@ -2,6 +2,7 @@ title: Typescript - Svelte description: Learn about the different types exported by the `@lucide/svelte` package and how to use them in your Svelte application. --- + # TypeScript Support List of exported types from the `@lucide/svelte` package. @@ -17,6 +18,10 @@ interface LucideProps extends SVGAttributes { color?: string; size?: number | string; strokeWidth?: number | string; + nonScalingStroke?: boolean; + /** + * @deprecated + */ absoluteStrokeWidth?: boolean; children?: Snippet; [key: string]: any; // Any other SVG attributes @@ -24,9 +29,11 @@ interface LucideProps extends SVGAttributes { ``` ### Using `LucideProps` + You can use the `LucideProps` interface to type props for your custom icon components. ::: code-group + ```svelte [IconWrapper.svelte] @@ -24,21 +25,22 @@ import FolderLock from "@lucide/svelte/icons/folder-lock"; ``` + ::: -## Absolute stroke width +## Non-scaling strokes -When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `absoluteStrokeWidth` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. +When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `nonScalingStroke` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. -This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. +This means that when `nonScalingStroke` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes. -![Absolute stroke width comparison](../../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison") + -### Adjusting stroke width with `absoluteStrokeWidth` prop +### Adjusting stroke width with `nonScalingStroke` prop -Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute. +Setting `nonScalingStroke` to `true` will make the stroke width non-scaling. ::: sandpack {template=vite-svelte editorHeight=240 editorWidthPercentage=60} @@ -49,7 +51,7 @@ import RollerCoaster from "@lucide/svelte/icons/roller-coaster"; ``` diff --git a/docs/guide/svelte/getting-started.md b/docs/guide/svelte/getting-started.md index c32d41eeb..8a9cf1f24 100644 --- a/docs/guide/svelte/getting-started.md +++ b/docs/guide/svelte/getting-started.md @@ -2,6 +2,7 @@ title: Getting started - Svelte description: This guide will help you get started with Lucide in your Svelte project. --- + @@ -15,22 +16,24 @@ This is useful if you want to create custom icons by combining existing ones. ```vue src/App.vue [active] - ``` ::: @@ -54,7 +57,7 @@ For example, you can add a notification badge to an icon by using the `circle` S ```vue src/App.vue [active] @@ -73,7 +76,6 @@ const hasUnreadMessages = true; - ``` ::: @@ -86,7 +88,7 @@ You can also use the `text` SVG element to add text to your icon. ```vue src/App.vue [active] - ``` ::: diff --git a/docs/guide/vue/advanced/global-styling.md b/docs/guide/vue/advanced/global-styling.md index eb7ce6ed5..25070059f 100644 --- a/docs/guide/vue/advanced/global-styling.md +++ b/docs/guide/vue/advanced/global-styling.md @@ -2,6 +2,7 @@ title: Global Styling - Vue description: Learn how to style all Lucide icons globally in your Vue application using CSS or the Lucide context provider. --- + @@ -74,9 +75,9 @@ import { Sandwich, Wine, Dessert, -} from "@lucide/vue"; +} from '@lucide/vue'; -import "./icon.css"; +import './icon.css'; ``` + ::: -### Absolute stroke width +### Non-scaling strokes -For global absolute stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [absolute-stroke-width](../basics/stroke-width.md#absolute-stroke-width) for more info. +For global non-scaling stroke width styling the `vector-effect: non-scaling-stroke` CSS property can be applied to the children. This will keep the stroke-width the same size no matter the size of the icon. See [non-scaling strokes](../basics/stroke-width.md#non-scaling-strokes) for more info. ::: sandpack {template=vue editorHeight=300 editorWidthPercentage=55 dependencies="@lucide/vue"} @@ -132,9 +134,9 @@ import { Map, CloudMoon, Sparkles, -} from "@lucide/vue"; +} from '@lucide/vue'; -import "./icon.css"; +import './icon.css'; - ``` + ::: diff --git a/docs/guide/vue/advanced/typescript.md b/docs/guide/vue/advanced/typescript.md index 4114c1247..096cb0ade 100644 --- a/docs/guide/vue/advanced/typescript.md +++ b/docs/guide/vue/advanced/typescript.md @@ -2,6 +2,7 @@ title: Typescript - Vue description: Learn about the different types exported by the `@lucide/vue` package and how to use them in your Vue application. --- + # TypeScript Support List of exported types from the `@lucide/vue` package. @@ -16,15 +17,21 @@ interface LucideProps { size?: number | string; color?: string; strokeWidth?: number; + nonScalingStroke?: boolean; + /** + * @deprecated + */ absoluteStrokeWidth?: boolean; [key: string]: any; // Any other SVG attributes } ``` ### Using `LucideProps` + You can use the `LucideProps` interface to type props for your custom icon components. ::: code-group + ```vue [IconWrapper.vue] ``` + ::: diff --git a/docs/guide/vue/basics/stroke-width.md b/docs/guide/vue/basics/stroke-width.md index c41ac174f..693f9db08 100644 --- a/docs/guide/vue/basics/stroke-width.md +++ b/docs/guide/vue/basics/stroke-width.md @@ -1,7 +1,8 @@ --- title: Stroke width - Vue -description: Learn how to adjust the stroke width of icons in your Vue application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `absoluteStrokeWidth` prop. +description: Learn how to adjust the stroke width of icons in your Vue application using the `strokeWidth` prop or adjust the strokeWidth appearance using the `nonScalingStroke` prop. --- + @@ -19,42 +20,43 @@ The `strokeWidth` can be adjusted to create a different look of the icons. ```vue src/App.vue [active] ``` + ::: -## Absolute stroke width +## Non-scaling strokes -When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `absoluteStrokeWidth` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. +When adjusting the `size` prop the size of the stroke width will be relative to the size of the icon, this is the default SVG behavior. The `nonScalingStroke` prop is introduced to adjust this behavior to make the stroke width constant no matter the size of the icon. -This means that when `absoluteStrokeWidth` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. +This means that when `nonScalingStroke` is enabled and the `size` of the icons is set to `48px` the `strokeWidth` will still be `2px` on the screen. Note `2px` is the default stroke width for a Lucide icon, this can be adjusted to all sizes. -![Absolute stroke width comparison](../../../images/absolute-stroke-width-compare.png?raw=true "Absolute stroke width comparison") + -### Adjusting stroke width with `absoluteStrokeWidth` prop +### Adjusting stroke width with `nonScalingStroke` prop -Setting `absoluteStrokeWidth` to `true` will make the stroke width absolute. +Setting `nonScalingStroke` to `true` will make the stroke width non-scaling. ::: sandpack {template=vue showTabs=false editorHeight=320 editorWidthPercentage=60 dependencies="@lucide/vue"} ```vue src/App.vue [active] - ``` + ::: diff --git a/docs/guide/vue/getting-started.md b/docs/guide/vue/getting-started.md index 277071d51..ce22268b9 100644 --- a/docs/guide/vue/getting-started.md +++ b/docs/guide/vue/getting-started.md @@ -2,6 +2,7 @@ title: Getting started - Vue description: This guide will help you get started with Lucide in your Vue project. --- +
diff --git a/docs/showcase.md b/docs/showcase.md index c8884e6ff..3a7c2ea15 100644 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -5,8 +5,8 @@ outline: 2 outlineTitle: Showcase ---
diff --git a/docs/vercel.json b/docs/vercel.json index 623d457e6..b67281451 100644 --- a/docs/vercel.json +++ b/docs/vercel.json @@ -13,7 +13,7 @@ }, { "source": "/docs/:slug(lucide-?.*)", - "destination": "/guide/packages/:slug", + "destination": "/guide/:slug", "permanent": true }, { @@ -58,48 +58,13 @@ "permanent": true }, { - "source": "/guide/packages/lucide-react", - "destination": "/guide/react", + "source": "/guide/packages/lucide-:package", + "destination": "/guide/:package", "permanent": true }, { - "source": "/guide/packages/lucide-vue-next", - "destination": "/guide/vue", - "permanent": true - }, - { - "source": "/guide/packages/lucide-vue", - "destination": "/guide/vue", - "permanent": true - }, - { - "source": "/guide/packages/lucide-svelte", - "destination": "/guide/svelte", - "permanent": true - }, - { - "source": "/guide/packages/lucide-solid", - "destination": "/guide/solid", - "permanent": true - }, - { - "source": "/guide/packages/lucide-angular", - "destination": "/guide/angular", - "permanent": true - }, - { - "source": "/guide/packages/lucide-preact", - "destination": "/guide/preact", - "permanent": true - }, - { - "source": "/guide/packages/lucide-astro", - "destination": "/guide/astro", - "permanent": true - }, - { - "source": "/guide/packages/lucide-static", - "destination": "/guide/static", + "source": "/guide/packages/:package", + "destination": "/guide/:package", "permanent": true }, { diff --git a/package.json b/package.json index 0c1a28710..5604d3a27 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "ajv-cli": "^5.0.0", + "cpy-cli": "^7.0.0", "cspell": "^10.0.1", "dotenv": "^17.4.2", "eslint": "^10.8.0", diff --git a/packages/angular/package.json b/packages/angular/package.json index 05688d51c..e4a6de9eb 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -17,14 +17,15 @@ "scripts": { "ng": "ng", "watch": "ng build --watch --configuration development", - "prebuild": "pnpm clean && pnpm copy:license && pnpm build:icons", + "prebuild": "pnpm clean && pnpm copy:license && pnpm copy:utils && pnpm build:icons", "build": "pnpm prebuild && pnpm build:ng", "copy:license": "cp ../../LICENSE ./LICENSE", + "copy:utils": "cpy ../../packages/shared/src/build/{buildLucideIconNode.ts,defaultAttributes.ts,types.ts} ../../packages/shared/src/utils/mergeClasses.ts ./src/utils/", "clean": "rm -rf dist && rm -rf ./src/icons/*.ts", "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --iconFileExtension=.ts --exportFileName=lucide-angular.ts --useDefaultExports=0", "build:ng": "ng build --configuration production", - "test": "pnpm build:icons && ng test --no-watch", - "test:watch": "ng test", + "test": "pnpm copy:utils && pnpm build:icons && ng test --no-watch", + "test:watch": "pnpm copy:utils && ng test", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx,html,css,scss}' --quiet --fix", "e2e": "ng e2e" }, diff --git a/packages/angular/src/lucide-config.ts b/packages/angular/src/lucide-config.ts index 8a4953127..3944638f1 100644 --- a/packages/angular/src/lucide-config.ts +++ b/packages/angular/src/lucide-config.ts @@ -19,6 +19,11 @@ export interface LucideConfig { * @default 2 */ strokeWidth: number; + /** + * Whether to use absolute stroke width. + * @deprecated Use `nonScalingStroke` instead. + */ + absoluteStrokeWidth: boolean; /** * Whether stroke width should be scaled to appear uniform regardless of icon size. * @default false @@ -31,7 +36,7 @@ export interface LucideConfig { * } * ``` */ - absoluteStrokeWidth: boolean; + nonScalingStroke: boolean; } /** @@ -42,6 +47,7 @@ export const lucideDefaultConfig: LucideConfig = { size: 24, strokeWidth: 2, absoluteStrokeWidth: false, + nonScalingStroke: false, }; /** diff --git a/packages/angular/src/lucide-hydration.spec.ts b/packages/angular/src/lucide-hydration.spec.ts index 8521fa8e0..9b4757c85 100644 --- a/packages/angular/src/lucide-hydration.spec.ts +++ b/packages/angular/src/lucide-hydration.spec.ts @@ -8,7 +8,7 @@ import { LucideBadgeAlert } from './icons/badge-alert'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector selector: 'lucide-static-hydration-root', - template: ``, + template: ``, imports: [LucideBadgeAlert], }) class StaticHydrationRootComponent {} diff --git a/packages/angular/src/lucide-icon-base.spec.ts b/packages/angular/src/lucide-icon-base.spec.ts index f90f91596..f5df57065 100644 --- a/packages/angular/src/lucide-icon-base.spec.ts +++ b/packages/angular/src/lucide-icon-base.spec.ts @@ -47,6 +47,7 @@ describe('LucideIconBase', () => { let size: WritableSignal; let strokeWidth: WritableSignal; let absoluteStrokeWidth: WritableSignal; + let nonScalingStroke: WritableSignal; const getSvgAttribute = (attr: string) => fixture.nativeElement.getAttribute(attr); const expectSvgClasses = (classes: string[], svg: SVGElement = fixture.nativeElement) => { for (const cssClass of classes) { @@ -63,6 +64,7 @@ describe('LucideIconBase', () => { inputBinding('size', size), inputBinding('strokeWidth', strokeWidth), inputBinding('absoluteStrokeWidth', absoluteStrokeWidth), + inputBinding('nonScalingStroke', nonScalingStroke), ], }); } @@ -72,6 +74,7 @@ describe('LucideIconBase', () => { size = signal(undefined); strokeWidth = signal(undefined); absoluteStrokeWidth = signal(undefined); + nonScalingStroke = signal(undefined); fixture = createComponent(); component = fixture.componentInstance; }); @@ -135,6 +138,11 @@ describe('LucideIconBase', () => { expect(getSvgAttribute('width')).toBe('18'); expect(getSvgAttribute('height')).toBe('18'); }); + it('should set viewBox', () => { + size.set('18'); + fixture.detectChanges(); + expect(getSvgAttribute('viewBox')).toBe('0 0 24 24'); + }); }); describe('strokeWidth', () => { @@ -155,22 +163,38 @@ describe('LucideIconBase', () => { }); describe('absoluteStrokeWidth', () => { + it('should adjust stroke width', () => { + strokeWidth.set(2); + size.set(32); + absoluteStrokeWidth.set(true); + fixture.detectChanges(); + expect(getSvgAttribute('stroke-width')).toBe('1.5'); + }); + it('should not set vector-effect on children', () => { + absoluteStrokeWidth.set(true); + for (const child of fixture.nativeElement.children) { + expect(child.getAttribute('vector-effect')).toBeNull(); + } + }); + }); + + describe('nonScalingStroke', () => { it('should not adjust stroke width', () => { strokeWidth.set(2); size.set(12); - absoluteStrokeWidth.set(true); + nonScalingStroke.set(false); fixture.detectChanges(); expect(getSvgAttribute('stroke-width')).toBe('2'); }); it('should not set vector-effect on children', () => { - absoluteStrokeWidth.set(false); + nonScalingStroke.set(false); fixture.detectChanges(); for (const child of getRenderedChildren()) { expect(child.getAttribute('vector-effect')).toBeNull(); } }); it('should set vector-effect on children', () => { - absoluteStrokeWidth.set(true); + nonScalingStroke.set(true); fixture.detectChanges(); for (const child of getRenderedChildren()) { expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke'); @@ -207,6 +231,7 @@ describe('LucideIconBase', () => { color: 'red', strokeWidth: 1, size: 12, + nonScalingStroke: true, absoluteStrokeWidth: true, }), ], @@ -229,12 +254,12 @@ describe('LucideIconBase', () => { describe('strokeWidth', () => { it('should use stroke width from config', () => { fixture.detectChanges(); - expect(getSvgAttribute('stroke-width')).toBe('1'); + expect(getSvgAttribute('stroke-width')).toBe('2'); }); it('should use override stroke width from config', () => { strokeWidth.set(3); fixture.detectChanges(); - expect(getSvgAttribute('stroke-width')).toBe('3'); + expect(getSvgAttribute('stroke-width')).toBe('6'); }); }); describe('size', () => { @@ -253,14 +278,23 @@ describe('LucideIconBase', () => { describe('absoluteStrokeWidth', () => { it('should use absoluteStrokeWidth from config', () => { fixture.detectChanges(); - for (const child of getRenderedChildren()) { - expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke'); - } + expect(getSvgAttribute('stroke-width')).toBe('2'); }); it('should override absoluteStrokeWidth', () => { absoluteStrokeWidth.set(false); fixture.detectChanges(); - for (const child of getRenderedChildren()) { + expect(getSvgAttribute('stroke-width')).toBe('1'); + }); + }); + describe('nonScalingStroke', () => { + it('should use nonScalingStroke from config', () => { + for (const child of fixture.nativeElement.children) { + expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke'); + } + }); + it('should override nonScalingStroke', () => { + nonScalingStroke.set(false); + for (const child of fixture.nativeElement.children) { expect(child.getAttribute('vector-effect')).toBeNull(); } }); diff --git a/packages/angular/src/lucide-icon-base.ts b/packages/angular/src/lucide-icon-base.ts index 5708baab6..624307076 100644 --- a/packages/angular/src/lucide-icon-base.ts +++ b/packages/angular/src/lucide-icon-base.ts @@ -1,8 +1,9 @@ import { Component, computed, inject, input, Signal } from '@angular/core'; import { LUCIDE_CONFIG } from './lucide-config'; -import { LucideIconData, Nullable } from './types'; +import { LucideIconData, LucideIconNode, Nullable } from './types'; import defaultAttributes from './default-attributes'; import { lucideIconTemplate } from './lucide-icon-template'; +import buildLucideIconNode from './utils/buildLucideIconNode'; /** * @internal @@ -12,38 +13,17 @@ import { lucideIconTemplate } from './lucide-icon-template'; template: lucideIconTemplate, host: { ...defaultAttributes, - class: 'lucide', - '[class]': 'iconClasses()', - '[attr.width]': 'size()', - '[attr.height]': 'size()', - '[attr.stroke]': 'color()', - '[attr.stroke-width]': 'strokeWidth()', - '[attr.aria-hidden]': '!title()', + '[attr.class]': 'computedClass()', + '[attr.width]': 'computedWidth()', + '[attr.height]': 'computedHeight()', + '[attr.viewBox]': 'computedViewbox()', + '[attr.stroke]': 'computedStroke()', + '[attr.stroke-width]': 'computedStrokeWidth()', + '[attr.aria-hidden]': 'computedAriaHidden()', }, }) export abstract class LucideIconBase { protected abstract readonly icon: Signal>; - protected readonly iconNodes = computed(() => { - const node = this.icon()?.node ?? []; - if (!this.absoluteStrokeWidth()) { - return node; - } - return node.map(([name, attrs]) => [ - name, - { - 'vector-effect': 'non-scaling-stroke', - ...attrs, - }, - ]); - }); - protected readonly iconClasses = computed(() => { - const icon = this.icon(); - if (!icon) { - return ''; - } - const { name, aliases = [] } = icon; - return [name, ...aliases].map((item) => `lucide-${item}`).join(' '); - }); protected readonly iconConfig = inject(LUCIDE_CONFIG); /** * An optional accessible label for the icon. @@ -58,6 +38,10 @@ export abstract class LucideIconBase { * - If your icon is functional (e.g. used in place of a label), feel free to use this property. */ readonly title = input>(); + /** + * Optional CSS classes for the icon. + */ + readonly class = input>(); /** * Width and height. * @default 24 @@ -65,6 +49,16 @@ export abstract class LucideIconBase { readonly size = input(this.iconConfig.size, { transform: (value: Nullable) => value ?? this.iconConfig.size, }); + /** + * Icon width. + * @default 24 + */ + readonly width = input(null); + /** + * Icon height. + * @default 24 + */ + readonly height = input(null); /** * Stroke color. * @default currentColor @@ -80,9 +74,95 @@ export abstract class LucideIconBase { transform: (value: Nullable) => value ?? this.iconConfig.strokeWidth, }); /** - * If set to true, it adds [`vector-effect="non-scaling-stroke"`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/vector-effect) to child elements. + * Whether to use absolute stroke width. */ readonly absoluteStrokeWidth = input(this.iconConfig.absoluteStrokeWidth, { transform: (value: Nullable) => value ?? this.iconConfig.absoluteStrokeWidth, }); + /** + * If set to true, it adds [`vector-effect="non-scaling-stroke"`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/vector-effect) to child elements. + */ + readonly nonScalingStroke = input(this.iconConfig.nonScalingStroke, { + transform: (value: Nullable) => value ?? this.iconConfig.nonScalingStroke, + }); + + /** + * @internal + */ + protected readonly computedIcon = computed(() => { + const icon = this.icon(); + return icon + ? buildLucideIconNode(icon, { + width: this.width() ?? this.size(), + height: this.height() ?? this.size(), + color: this.color(), + strokeWidth: this.strokeWidth(), + absoluteStrokeWidth: this.absoluteStrokeWidth(), + nonScalingStroke: this.nonScalingStroke(), + className: this.class() ?? undefined, + hasA11yProp: this.title() != null, + }) + : null; + }); + + /** + * @internal + */ + protected readonly computedAttributes = computed( + () => this.computedIcon()?.[1], + ); + + /** + * @internal + */ + protected readonly computedIconNode = computed(() => this.computedIcon()?.[2] ?? []); + + /** + * @internal + */ + protected readonly computedWidth = computed( + () => this.computedAttributes()?.['width'] ?? this.iconConfig.size, + ); + + /** + * @internal + */ + protected readonly computedHeight = computed( + () => this.computedAttributes()?.['height'] ?? this.iconConfig.size, + ); + + /** + * @internal + */ + protected readonly computedStroke = computed( + () => this.computedAttributes()?.['stroke'] ?? this.iconConfig.color, + ); + + /** + * @internal + */ + protected readonly computedStrokeWidth = computed( + () => this.computedAttributes()?.['stroke-width'] ?? this.iconConfig.strokeWidth, + ); + + /** + * @internal + */ + protected readonly computedAriaHidden = computed( + () => this.computedAttributes()?.['aria-hidden'] === 'true', + ); + + /** + * @internal + */ + protected readonly computedViewbox = computed( + () => this.computedAttributes()?.['viewBox'] ?? '0 0 24 24', + ); + + /** + * @internal + */ + protected readonly computedClass = computed( + () => this.computedAttributes()?.['class'] ?? 'lucide', + ); } diff --git a/packages/angular/src/lucide-icon-template.ts b/packages/angular/src/lucide-icon-template.ts index ec95dc55f..5a6cde461 100644 --- a/packages/angular/src/lucide-icon-template.ts +++ b/packages/angular/src/lucide-icon-template.ts @@ -6,7 +6,7 @@ export const lucideIconTemplate = `@if (title(); as titleValue) { {{ titleValue }} } -@for (child of iconNodes(); track child[1]['key'] ?? $index) { +@for (child of computedIconNode(); track child[1]['key'] ?? $index) { @let attrs = child[1]; @switch (child[0]) { @case ('path') { diff --git a/packages/angular/src/types.ts b/packages/angular/src/types.ts index bc75de19e..c234ab405 100644 --- a/packages/angular/src/types.ts +++ b/packages/angular/src/types.ts @@ -1,18 +1,16 @@ import { Signal, Type } from '@angular/core'; +import { + LucideIconNode as SharedLucideIconNode, + LucideIconData as SharedLucideIconData, +} from './utils/types'; -type HtmlAttributes = { [key: string]: string | number }; -export type LucideIconNode = readonly [string, HtmlAttributes]; +export type LucideIconNode = SharedLucideIconNode; export type LucideIcons = { [key: string]: LucideIconData }; /** * A Lucide icon object that fully describes an icon to be displayed. */ -export type LucideIconData = { - name: string; - size?: number; - node: LucideIconNode[]; - aliases?: string[]; -}; +export type LucideIconData = SharedLucideIconData & { name: string }; /** * Input signal map of Lucide icon components. @@ -22,7 +20,11 @@ interface LucideIconProps { size: Signal>; color: Signal>; strokeWidth: Signal>; + /** + * @deprecated Use `nonScalingStroke` instead. + */ absoluteStrokeWidth: Signal>; + nonScalingStroke: Signal>; } /** diff --git a/packages/astro/package.json b/packages/astro/package.json index f55794442..24bed5562 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -43,7 +43,7 @@ "build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts --pretty=false", "clean": "rm -rf dist && rm -rf stats && rm -rf ./src/icons/*.ts", "copy:license": "cp ../../LICENSE ./LICENSE", - "copy:utils": "mkdir -p ./src/utils && for f in hasA11yProp toKebabCase mergeClasses; do cp -f ../../packages/shared/src/utils/$f.ts ./src/utils/; done", + "copy:utils": "cpy ../../packages/shared/src/build/{buildLucideIconNode.ts,defaultAttributes.ts,types.ts} ../../packages/shared/src/utils/{hasA11yProp.ts,toKebabCase.ts,mergeClasses.ts} ./src/utils/", "test": "pnpm copy:utils && pnpm build:icons && vitest run", "test:watch": "pnpm build:icons && vitest run --watch" }, diff --git a/packages/astro/scripts/exportTemplate.mts b/packages/astro/scripts/exportTemplate.mts index c8a41ed3a..1c43db3e4 100644 --- a/packages/astro/scripts/exportTemplate.mts +++ b/packages/astro/scripts/exportTemplate.mts @@ -1,28 +1,27 @@ import base64SVG from '@lucide/build-icons/utils/base64SVG'; import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate'; -export default defineExportTemplate(async ({ - componentName, - iconName, - children, - getSvg, - deprecated, - deprecationReason, -}) => { - const svgContents = await getSvg(); - const svgBase64 = base64SVG(svgContents); +export default defineExportTemplate( + async ({ componentName, iconName, iconData, getSvg, deprecated, deprecationReason }) => { + const svgContents = await getSvg(); + const svgBase64 = base64SVG(svgContents); - // Astro doesn't need keyed children in loops - const keylessChildren = children.map((c) => { - const [element, { key, ...otherAttrs }] = c; - return [element, otherAttrs]; - }); + // Astro doesn't need keyed children in loops + const keylessIconData = { + ...iconData, + node: iconData.node.map((child) => { + const [element, { key, ...otherAttrs }] = child; + return [element, otherAttrs]; + }), + }; - // TODO: build-icons' `pretty` is set to false as the prettier - // formatter uses babel which I'm not sure it supports typescript - return ` + // TODO: build-icons' `pretty` is set to false as the prettier + // formatter uses babel which I'm not sure it supports typescript + return ` import createLucideIcon from '../createLucideIcon'; -import type { AstroComponent } from '../types' +import type { AstroComponent, LucideIconData } from '../types' + +const iconData: LucideIconData = ${JSON.stringify(keylessIconData)}; /** * @component @name ${componentName} @@ -35,8 +34,9 @@ import type { AstroComponent } from '../types' * @returns {any} Astro Component * ${deprecated ? `@deprecated ${deprecationReason}` : ''} */ -const ${componentName} = createLucideIcon('${iconName}', ${JSON.stringify(keylessChildren)}) as AstroComponent; +const ${componentName} = createLucideIcon(iconData) as AstroComponent; export default ${componentName}; `; -}); + }, +); diff --git a/packages/astro/src/Icon.astro b/packages/astro/src/Icon.astro index eb504dbad..7d54bedfa 100644 --- a/packages/astro/src/Icon.astro +++ b/packages/astro/src/Icon.astro @@ -1,32 +1,44 @@ --- -import defaultAttributes from './defaultAttributes'; +import buildLucideIconNode from './utils/buildLucideIconNode'; import type { IconProps as Props } from './types'; import { hasA11yProp } from './utils/hasA11yProp'; - const { color = 'currentColor', size = 24, + width, + height, 'stroke-width': strokeWidth = 2, absoluteStrokeWidth = false, - iconNode = [], + nonScalingStroke = false, + iconNode, + icon = { + node: iconNode, + aliases: [], + size: 24, + }, class: className, ...rest } = Astro.props; + +const [, builtIconAttributes, builtIconNode = []] = buildLucideIconNode(icon, { + color, + width: width ?? size, + height: height ?? size, + strokeWidth, + absoluteStrokeWidth, + nonScalingStroke, + className, + hasA11yProp: hasA11yProp(rest), +}); + +const iconAttributes = { + ...builtIconAttributes, + ...rest, +}; --- - - {iconNode.map(([Tag, attrs]) => )} + + {builtIconNode.map(([Tag, attrs]) => )} diff --git a/packages/astro/src/createLucideIcon.ts b/packages/astro/src/createLucideIcon.ts index 36c1c99e7..7d5b2a594 100644 --- a/packages/astro/src/createLucideIcon.ts +++ b/packages/astro/src/createLucideIcon.ts @@ -1,11 +1,43 @@ import type { AstroComponentFactory } from 'astro/runtime/server/render/astro/factory.js'; -import type { IconNode } from './types'; +import type { LucideIconData, LucideIconNode } from './types'; import { render, renderSlot, createComponent, renderComponent } from 'astro/compiler-runtime'; import Icon from './Icon.astro'; -import { mergeClasses } from './utils/mergeClasses'; -import { toKebabCase } from './utils/toKebabCase'; -export default (iconName: string, iconNode: IconNode): AstroComponentFactory => { +/** + * Creates a Lucide icon component from icon data. + * + * @param icon Icon data object. + * @returns Lucide icon component. + */ +function createLucideIcon(icon: LucideIconData): AstroComponentFactory; + +/** + * Creates a Lucide icon component from the legacy icon arguments. + * + * @param iconName Icon name. + * @param iconNode Icon node. + * @param aliases Optional icon aliases. + * @returns Lucide icon component. + */ +function createLucideIcon( + iconName: string, + iconNode: LucideIconNode[], + aliases?: string[], +): AstroComponentFactory; + +function createLucideIcon( + iconOrIconName: string | LucideIconData, + iconNode: LucideIconNode[] = [], + aliases: string[] = [], +): AstroComponentFactory { + const icon = + typeof iconOrIconName === 'string' + ? { + name: iconOrIconName, + node: iconNode, + aliases, + } + : iconOrIconName; const Component = createComponent( ($$result, $$props: Record, $$slots) => { const { class: className, ...restProps } = $$props; @@ -14,11 +46,8 @@ export default (iconName: string, iconNode: IconNode): AstroComponentFactory => 'Icon', Icon, { - class: mergeClasses( - Boolean(iconName) && `lucide-${toKebabCase(iconName)}`, - Boolean(className) && className, - ), - iconNode, + class: className, + icon, ...restProps, }, { default: () => render`${renderSlot($$result, $$slots['default'])}` }, @@ -28,4 +57,6 @@ export default (iconName: string, iconNode: IconNode): AstroComponentFactory => 'none', ); return Component; -}; +} + +export default createLucideIcon; diff --git a/packages/astro/src/defaultAttributes.ts b/packages/astro/src/defaultAttributes.ts deleted file mode 100644 index 5b1fddd06..000000000 --- a/packages/astro/src/defaultAttributes.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { SVGAttributes } from './types'; - -const defaultAttributes: SVGAttributes = { - 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', -}; - -export default defaultAttributes; diff --git a/packages/astro/src/lucide-astro.ts b/packages/astro/src/lucide-astro.ts index adc0c1003..19d041177 100644 --- a/packages/astro/src/lucide-astro.ts +++ b/packages/astro/src/lucide-astro.ts @@ -3,6 +3,6 @@ export * as icons from './icons/index'; export * from './aliases'; export * from './types'; -export { default as defaultAttributes } from './defaultAttributes'; +export { default as defaultAttributes } from './utils/defaultAttributes'; export { default as createLucideIcon } from './createLucideIcon'; export { default as Icon } from './Icon.astro'; diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 451e3d181..aff54253b 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -1,23 +1,47 @@ import type { HTMLAttributes } from 'astro/types'; +import type { + LucideIconData as SharedLucideIconData, + LucideIconNode as SharedLucideIconNode, +} from './utils/types'; // Type that the Astro language server needs to infer component props in Astro files -export type AstroComponent = (_props: IconProps) => unknown; +export type AstroComponent = (_props: LucideProps) => unknown; -export interface IconProps extends SVGAttributes { +export type LucideProps = SVGAttributes & { color?: string; size?: number | string; 'stroke-width'?: number | string; + /** + * @deprecated Use `nonScalingStroke` instead. + */ absoluteStrokeWidth?: boolean; + nonScalingStroke?: boolean; class?: string; - iconNode?: IconNode; title?: string; -} +}; + +export type IconProps = LucideProps & + ( + | { + icon: LucideIconData; + iconNode?: never; + } + | { + icon?: never; + iconNode: LucideIconNode[]; + } + ); export type SVGAttributes = HTMLAttributes<'svg'>; -export type IconNode = IconNodeChild[]; +export type LucideIconNode = SharedLucideIconNode; -type IconNodeChild = [elementName: SVGElements, attrs: SVGAttributes]; +export type LucideIconData = SharedLucideIconData; + +/** + * @deprecated Use LucideIconNode instead. + */ +export type IconNode = LucideIconNode[]; // All possible svg elements according to the Astro definitions type SVGElements = diff --git a/packages/astro/tests/__snapshots__/Icon.spec.ts.snap b/packages/astro/tests/__snapshots__/Icon.spec.ts.snap index a550ccb86..f3bc1ec4d 100644 --- a/packages/astro/tests/__snapshots__/Icon.spec.ts.snap +++ b/packages/astro/tests/__snapshots__/Icon.spec.ts.snap @@ -10,8 +10,8 @@ exports[`Using Icon Component > should render icon and match snapshot 1`] = ` stroke-width="1" stroke-linecap="round" stroke-linejoin="round" - aria-hidden="true" class="lucide" + aria-hidden="true" > diff --git a/packages/astro/tests/__snapshots__/createLucideIcon.spec.ts.snap b/packages/astro/tests/__snapshots__/createLucideIcon.spec.ts.snap index 13a285420..e53acc2bb 100644 --- a/packages/astro/tests/__snapshots__/createLucideIcon.spec.ts.snap +++ b/packages/astro/tests/__snapshots__/createLucideIcon.spec.ts.snap @@ -10,8 +10,8 @@ exports[`Using createLucideIcon > should create a component from an iconNode 1`] stroke-width="2" stroke-linecap="round" stroke-linejoin="round" - aria-hidden="true" class="lucide lucide-air-vent" + aria-hidden="true" > diff --git a/packages/astro/tests/__snapshots__/lucide-astro.spec.ts.snap b/packages/astro/tests/__snapshots__/lucide-astro.spec.ts.snap index 35f95548d..34154c0eb 100644 --- a/packages/astro/tests/__snapshots__/lucide-astro.spec.ts.snap +++ b/packages/astro/tests/__snapshots__/lucide-astro.spec.ts.snap @@ -10,9 +10,9 @@ exports[`Using lucide icon components > should add a non-default attribute to th stroke-width="2" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-face-slightly-smiling lucide-smile" aria-hidden="true" style="position: absolute" - class="lucide lucide-face-slightly-smiling" > @@ -38,8 +38,8 @@ exports[`Using lucide icon components > should adjust the size, stroke color and stroke-width="4" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3" aria-hidden="true" - class="lucide lucide-grid-3x3" > should apply all classes to the element stroke-width="2" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-droplet lucide-drop my-icon" aria-hidden="true" - class="lucide lucide-droplet my-icon" > `; -exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = ` +exports[`Using lucide icon components > should apply vector-effect when nonScalingStroke is set 1`] = ` `; @@ -118,8 +127,8 @@ exports[`Using lucide icon components > should pass children to the icon slot 1` stroke-width="2" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-face-slightly-smiling lucide-smile" aria-hidden="true" - class="lucide lucide-face-slightly-smiling" > @@ -148,8 +157,8 @@ exports[`Using lucide icon components > should render a component 1`] = ` stroke-width="2" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3" aria-hidden="true" - class="lucide lucide-grid-3x3" > should render the icon with default attr stroke-width="2" stroke-linecap="round" stroke-linejoin="round" + class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3" + aria-hidden="true" +> + + + + + + + + + + + +`; + +exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = ` +
`; -exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = ` +exports[`Using lucide icon components > should apply vector-effect when nonScalingStroke is set 1`] = `
should render an icon slot 1`] = `
`; + +exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = ` + +`; diff --git a/packages/svelte/tests/lucide-svelte.spec.ts b/packages/svelte/tests/lucide-svelte.spec.ts index da19e096b..4713a05bf 100644 --- a/packages/svelte/tests/lucide-svelte.spec.ts +++ b/packages/svelte/tests/lucide-svelte.spec.ts @@ -66,7 +66,7 @@ describe('Using lucide icon components', () => { expect(PenIconRenderedHTML).toBe(Edit2Container.innerHTML); }); - it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => { + it('should scale the strokeWidth when absoluteStrokeWidth is set', () => { const { container } = render(Smile, { color: 'red', size: 48, @@ -83,6 +83,24 @@ describe('Using lucide icon components', () => { expect(container.innerHTML).toMatchSnapshot(); }); + it('should apply vector-effect when nonScalingStroke is set', () => { + const { container } = render(Smile, { + color: 'red', + size: 48, + nonScalingStroke: true, + }); + + const IconComponent = container.firstElementChild; + + expect(IconComponent).toHaveAttribute('width', '48'); + expect(IconComponent).toHaveAttribute('height', '48'); + expect(IconComponent).toHaveAttribute('stroke', 'red'); + expect(IconComponent).toHaveAttribute('stroke-width', '2'); + expect(IconComponent?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke'); + + expect(container.innerHTML).toMatchSnapshot(); + }); + it('should use context values from the global set properties', () => { const { container } = render(ContextWrapper); diff --git a/packages/svelte/tests/setupVitest.ts b/packages/svelte/tests/setupVitest.ts index 03a0244af..01bf442b9 100644 --- a/packages/svelte/tests/setupVitest.ts +++ b/packages/svelte/tests/setupVitest.ts @@ -1,5 +1,6 @@ import { expect } from 'vitest'; -import '@testing-library/jest-dom/vitest'; +import * as matchers from '@testing-library/jest-dom/matchers'; import htmlSerializer from 'jest-serializer-html'; +expect.extend(matchers); expect.addSnapshotSerializer(htmlSerializer); diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json index 3ef7dda35..58280e442 100644 --- a/packages/svelte/tsconfig.json +++ b/packages/svelte/tsconfig.json @@ -6,6 +6,11 @@ "module": "NodeNext", "moduleResolution": "NodeNext", "resolveJsonModule": true, + "baseUrl": ".", + "paths": { + "@lucide/shared": ["../shared/src/index.ts"], + "@lucide/shared/types": ["../shared/src/build/types.ts"], + }, "allowJs": true, "checkJs": true, "isolatedModules": true, diff --git a/packages/vue/rollup.config.mjs b/packages/vue/rollup.config.mjs index 94bba42f8..b69ab424a 100644 --- a/packages/vue/rollup.config.mjs +++ b/packages/vue/rollup.config.mjs @@ -2,6 +2,11 @@ import plugins from '@lucide/rollup-plugins'; import pkg from './package.json' with { type: 'json' }; import dts from 'rollup-plugin-dts'; +const dtsOptions = { + includeExternal: ['@lucide/shared/types'], + tsconfig: './tsconfig.json', +}; + const packageName = '@lucide/vue'; const outputFileName = 'lucide-vue'; const outputDir = 'dist'; @@ -58,13 +63,7 @@ export default [ format: 'es', }, ], - plugins: [ - dts({ - compilerOptions: { - preserveSymlinks: false, - }, - }), - ], + plugins: [dts(dtsOptions)], }, { input: `src/${outputFileName}.suffixed.ts`, @@ -74,13 +73,7 @@ export default [ format: 'es', }, ], - plugins: [ - dts({ - compilerOptions: { - preserveSymlinks: false, - }, - }), - ], + plugins: [dts(dtsOptions)], }, { input: `src/${outputFileName}.prefixed.ts`, @@ -90,13 +83,7 @@ export default [ format: 'es', }, ], - plugins: [ - dts({ - compilerOptions: { - preserveSymlinks: false, - }, - }), - ], + plugins: [dts(dtsOptions)], }, ...configs, ]; diff --git a/packages/vue/scripts/exportTemplate.mts b/packages/vue/scripts/exportTemplate.mts index d42df2a61..6f85690b8 100644 --- a/packages/vue/scripts/exportTemplate.mts +++ b/packages/vue/scripts/exportTemplate.mts @@ -1,22 +1,16 @@ import base64SVG from '@lucide/build-icons/utils/base64SVG'; import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate'; -export default defineExportTemplate(async({ - componentName, - iconName, - children, - getSvg, - deprecated, - deprecationReason, -}) => { - const svgContents = await getSvg(); - const svgBase64 = base64SVG(svgContents); +export default defineExportTemplate( + async ({ componentName, iconName, iconData, getSvg, deprecated, deprecationReason }) => { + const svgContents = await getSvg(); + const svgBase64 = base64SVG(svgContents); - return ` + return ` import createLucideIcon from '../createLucideIcon'; -import { IconNode } from '../types'; +import type { LucideIconData } from '../types'; -export const __iconNode: IconNode = ${JSON.stringify(children)} +export const __iconData: LucideIconData = ${JSON.stringify(iconData)} /** * @component @name ${componentName} @@ -29,8 +23,9 @@ export const __iconNode: IconNode = ${JSON.stringify(children)} * @returns {FunctionalComponent} Vue component * ${deprecated ? `@deprecated ${deprecationReason}` : ''} */ -const ${componentName} = createLucideIcon('${iconName}', __iconNode); +const ${componentName} = createLucideIcon(__iconData); export default ${componentName}; `; -}); + }, +); diff --git a/packages/vue/src/Icon.ts b/packages/vue/src/Icon.ts index 8d4254460..f8b6d2aa8 100644 --- a/packages/vue/src/Icon.ts +++ b/packages/vue/src/Icon.ts @@ -1,24 +1,48 @@ -import { computed, type FunctionalComponent, h } from 'vue'; -import { isEmptyString, mergeClasses, toKebabCase, toPascalCase } from '@lucide/shared'; -import defaultAttributes from './defaultAttributes'; -import { IconNode, LucideProps } from './types'; +import { type FunctionalComponent, h } from 'vue'; +import { buildLucideIconNode, hasA11yProp, isEmptyString, toKebabCase } from '@lucide/shared'; +import { LucideIconNode, LucideIconData, LucideProps } from './types'; import { useLucideProps } from './context'; -type IconProps = { name: string } & ( - | { iconNode: IconNode; 'icon-node'?: never } - | { 'icon-node': IconNode; iconNode?: never } -); +type IconProps = + | { + icon: LucideIconData; + iconNode?: never; + 'icon-node'?: never; + name?: never; + } + | { + icon?: never; + iconNode: LucideIconNode[]; + 'icon-node'?: never; + name: string; + } + | { + icon?: never; + iconNode?: never; + 'icon-node': LucideIconNode[]; + name: string; + }; const Icon: FunctionalComponent = ( { name, iconNode, 'icon-node': iconNodeKebabCase, + icon = { + name: toKebabCase(name), + node: iconNode ?? iconNodeKebabCase, + size: 24, + aliases: [], + }, absoluteStrokeWidth, 'absolute-stroke-width': absoluteStrokeWidthKebabCase, + nonScalingStroke, + 'non-scaling-stroke': nonScalingStrokeKebabCase, strokeWidth, 'stroke-width': strokeWidthKebabCase, size, + width = size, + height = size, color, ...props }, @@ -29,54 +53,44 @@ const Icon: FunctionalComponent = ( color: contextColor, strokeWidth: contextStrokeWidth = 2, absoluteStrokeWidth: contextAbsoluteStrokeWidth = false, + nonScalingStroke: contextNonScalingStroke = false, class: contextClass = '', } = useLucideProps(); - const calculatedStrokeWidth = computed(() => { - const isAbsoluteStrokeWidth = - isEmptyString(absoluteStrokeWidth) || - isEmptyString(absoluteStrokeWidthKebabCase) || - absoluteStrokeWidth === true || - absoluteStrokeWidthKebabCase === true || - contextAbsoluteStrokeWidth === true; + const isAbsoluteStrokeWidth = + isEmptyString(absoluteStrokeWidth) || + isEmptyString(absoluteStrokeWidthKebabCase) || + absoluteStrokeWidth === true || + absoluteStrokeWidthKebabCase === true || + contextAbsoluteStrokeWidth === true; - const strokeWidthValue = - strokeWidth || - strokeWidthKebabCase || - contextStrokeWidth || - defaultAttributes['stroke-width']; + const isNonScalingStroke = + isEmptyString(nonScalingStroke) || + isEmptyString(nonScalingStrokeKebabCase) || + nonScalingStroke === true || + nonScalingStrokeKebabCase === true || + contextNonScalingStroke === true; - if (isAbsoluteStrokeWidth) { - return ( - (Number(strokeWidthValue) * 24) / Number(size ?? contextSize ?? defaultAttributes.width) - ); - } + delete props.class; - return strokeWidthValue; + const defaultSlot = slots.default?.(); + + const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(icon, { + color: color ?? contextColor, + width: width ?? size ?? contextSize, + height: height ?? size ?? contextSize, + strokeWidth: strokeWidth ?? strokeWidthKebabCase ?? contextStrokeWidth, + absoluteStrokeWidth: isAbsoluteStrokeWidth, + nonScalingStroke: isNonScalingStroke, + className: contextClass, + hasA11yProp: (defaultSlot != null && defaultSlot.length > 0) || hasA11yProp(props), + attributes: props, }); - return h( - 'svg', - { - ...defaultAttributes, - ...props, - width: size ?? contextSize ?? defaultAttributes.width, - height: size ?? contextSize ?? defaultAttributes.height, - stroke: color ?? contextColor ?? defaultAttributes.stroke, - 'stroke-width': calculatedStrokeWidth.value, - class: mergeClasses( - 'lucide', - contextClass, - ...(name - ? [`lucide-${toKebabCase(toPascalCase(name))}-icon`, `lucide-${toKebabCase(name)}`] - : ['lucide-icon']), - ), - }, - [ - ...(iconNode ?? iconNodeKebabCase ?? []).map((child) => h(...child)), - ...(slots.default ? [slots.default()] : []), - ], - ); + return h('svg', svgAttributes, [ + ...builtIconNode.map((child) => h(...child)), + ...(defaultSlot ?? []), + ]); }; export default Icon; diff --git a/packages/vue/src/context.ts b/packages/vue/src/context.ts index 7af325ec7..0435ed55c 100644 --- a/packages/vue/src/context.ts +++ b/packages/vue/src/context.ts @@ -6,7 +6,11 @@ interface LucideIconsContext { size?: number; color?: string; strokeWidth?: number; + /** + * @deprecated Use `nonScalingStroke` instead. + */ absoluteStrokeWidth?: boolean; + nonScalingStroke?: boolean; class?: string; } diff --git a/packages/vue/src/createLucideIcon.ts b/packages/vue/src/createLucideIcon.ts index 85919b106..5981b8153 100644 --- a/packages/vue/src/createLucideIcon.ts +++ b/packages/vue/src/createLucideIcon.ts @@ -1,25 +1,44 @@ import { h } from 'vue'; -import { IconNode, LucideIcon } from './types'; +import { LucideIcon, LucideIconData, LucideIconNode } from './types'; import Icon from './Icon'; /** - * Create a Lucide icon component - * @param {string} iconName - * @param {array} iconNode - * @returns {FunctionalComponent} LucideIcon + * Creates a Lucide icon component from icon data. + * + * @param icon Icon data object. + * @returns Lucide icon component. */ -const createLucideIcon = - (iconName: string, iconNode: IconNode): LucideIcon => - (props, { slots, attrs }) => +function createLucideIcon(icon: LucideIconData): LucideIcon; + +/** + * Creates a Lucide icon component from the legacy icon arguments. + * + * @param iconName Icon name. + * @param iconNode Icon node. + * @returns Lucide icon component. + */ +function createLucideIcon(iconName: string, iconNode: LucideIconNode[]): LucideIcon; + +function createLucideIcon( + iconDataOrName: LucideIconData | string, + iconNode: LucideIconNode[] = [], +): LucideIcon { + const icon: LucideIconData = + typeof iconDataOrName === 'string' + ? { + name: iconDataOrName, + node: iconNode, + } + : iconDataOrName; + return (props, { slots }) => h( Icon, { - ...attrs, ...props, - iconNode, - name: iconName, + icon, }, slots.default ? { default: slots.default } : undefined, ); +} export default createLucideIcon; diff --git a/packages/vue/src/defaultAttributes.ts b/packages/vue/src/defaultAttributes.ts index 8ae7d46ea..cc4036fbd 100644 --- a/packages/vue/src/defaultAttributes.ts +++ b/packages/vue/src/defaultAttributes.ts @@ -1,11 +1 @@ -export default { - 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', -}; +export { defaultAttributes as default } from '@lucide/shared'; diff --git a/packages/vue/src/types.ts b/packages/vue/src/types.ts index a8b865db6..f292be26a 100644 --- a/packages/vue/src/types.ts +++ b/packages/vue/src/types.ts @@ -1,11 +1,31 @@ import type { FunctionalComponent, SVGAttributes } from 'vue'; +import type { + LucideIconData as SharedLucideIconData, + LucideIconNode as SharedLucideIconNode, +} from '@lucide/shared/types'; export interface LucideProps extends Partial { size?: 24 | number; strokeWidth?: number | string; + /** + * @deprecated Use `nonScalingStroke` instead. + */ absoluteStrokeWidth?: boolean; + /** + * @deprecated Use `nonScalingStroke` instead. + */ 'absolute-stroke-width'?: boolean; + nonScalingStroke?: boolean; + 'non-scaling-stroke'?: boolean; } -export type IconNode = [elementName: string, attrs: Record][]; +export type LucideIconNode = SharedLucideIconNode; + +export type LucideIconData = SharedLucideIconData; + +/** + * @deprecated Use LucideIconNode instead. + */ +export type IconNode = LucideIconNode[]; + export type LucideIcon = FunctionalComponent; diff --git a/packages/vue/tests/Icon.spec.ts b/packages/vue/tests/Icon.spec.ts index d8ec174a8..134e34c8c 100644 --- a/packages/vue/tests/Icon.spec.ts +++ b/packages/vue/tests/Icon.spec.ts @@ -1,11 +1,14 @@ import { describe, it, expect } from 'vitest'; import { render } from '@testing-library/vue'; +import { defineComponent } from 'vue'; import { airVent } from './testIconNodes'; import { Icon } from '../src/lucide-vue'; describe('Using Icon Component', () => { - it('should render icon based on a iconNode', async () => { + const airVentIcon = { name: 'air-vent', node: airVent, size: 24 }; + + it('should render icon based on an icon node', async () => { const { container } = render(Icon, { props: { iconNode: airVent, @@ -16,7 +19,7 @@ describe('Using Icon Component', () => { }, }); - expect(container.firstChild).toBeDefined(); + expect(container.firstChild).toMatchSnapshot(); }); it('should render when iconNode is passed as the kebab-case icon-node prop', async () => { @@ -34,8 +37,7 @@ describe('Using Icon Component', () => { it('should render icon and match snapshot', async () => { const { container } = render(Icon, { props: { - iconNode: airVent, - name: 'AirVent', + icon: airVentIcon, size: 48, color: 'red', absoluteStrokeWidth: true, @@ -44,4 +46,27 @@ describe('Using Icon Component', () => { expect(container.firstChild).toMatchSnapshot(); }); + + it('should update when reactive props change', async () => { + const wrapper = defineComponent({ + components: { Icon }, + props: { + size: Number, + }, + setup() { + return { airVentIcon }; + }, + template: '', + }); + + const { container, rerender } = render(wrapper, { props: { size: 24 } }); + + expect(container.firstChild).toHaveAttribute('width', '24'); + expect(container.firstChild).toHaveAttribute('height', '24'); + + await rerender({ size: 48 }); + + expect(container.firstChild).toHaveAttribute('width', '48'); + expect(container.firstChild).toHaveAttribute('height', '48'); + }); }); diff --git a/packages/vue/tests/__snapshots__/Icon.spec.ts.snap b/packages/vue/tests/__snapshots__/Icon.spec.ts.snap index 4b742c143..9257657d8 100644 --- a/packages/vue/tests/__snapshots__/Icon.spec.ts.snap +++ b/packages/vue/tests/__snapshots__/Icon.spec.ts.snap @@ -2,7 +2,37 @@ exports[`Using Icon Component > should render icon and match snapshot 1`] = ` +`; + +exports[`Using Icon Component > should render icon based on an icon node 1`] = ` +
should pass children to the icon slot 1` cy="12" r="10" /> - Hello World -
`; @@ -137,7 +138,8 @@ exports[`Using lucide icon components > should pass children to the icon slot 1` exports[`Using lucide icon components > should render an component 1`] = `