mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-14 13:58:21 +00:00
feat(): unify icon build logic some more, add nonScalingStroke option, marking absoluteStrokeWidth as deprecated
This commit is contained in:
@@ -25,6 +25,7 @@ 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
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"@vitest/coverage-v8": "4.0.12",
|
||||
"@vitest/ui": "4.0.12",
|
||||
"ajv-cli": "^5.0.0",
|
||||
"cpy-cli": "^7.0.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"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": "mkdir -p ./src/utils && cp -f ../../packages/shared/src/build/buildLucideIconNode.ts ./src/utils/ && cp -f ../../packages/shared/src/build/types.ts ./src/utils/",
|
||||
"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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,7 @@ describe('LucideIconBase', () => {
|
||||
let size: WritableSignal<string | number | undefined>;
|
||||
let strokeWidth: WritableSignal<string | number | undefined>;
|
||||
let absoluteStrokeWidth: WritableSignal<boolean | undefined>;
|
||||
let nonScalingStroke: WritableSignal<boolean | undefined>;
|
||||
const getSvgAttribute = (attr: string) => fixture.nativeElement.getAttribute(attr);
|
||||
function createComponent() {
|
||||
return TestBed.createComponent(LucideCircleCheck, {
|
||||
@@ -51,6 +52,7 @@ describe('LucideIconBase', () => {
|
||||
inputBinding('size', size),
|
||||
inputBinding('strokeWidth', strokeWidth),
|
||||
inputBinding('absoluteStrokeWidth', absoluteStrokeWidth),
|
||||
inputBinding('nonScalingStroke', nonScalingStroke),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -60,6 +62,7 @@ describe('LucideIconBase', () => {
|
||||
size = signal(undefined);
|
||||
strokeWidth = signal(undefined);
|
||||
absoluteStrokeWidth = signal(undefined);
|
||||
nonScalingStroke = signal(undefined);
|
||||
fixture = createComponent();
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
@@ -113,6 +116,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', () => {
|
||||
@@ -133,21 +141,37 @@ 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);
|
||||
for (const child of fixture.nativeElement.children) {
|
||||
expect(child.getAttribute('vector-effect')).toBeNull();
|
||||
}
|
||||
});
|
||||
it('should set vector-effect on children', () => {
|
||||
absoluteStrokeWidth.set(true);
|
||||
nonScalingStroke.set(true);
|
||||
for (const child of fixture.nativeElement.children) {
|
||||
expect(child.getAttribute('vector-effect')).toBe('non-scaling-stroke');
|
||||
}
|
||||
@@ -205,12 +229,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', () => {
|
||||
@@ -239,5 +263,18 @@ describe('LucideIconBase', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
@@ -9,7 +10,7 @@ import {
|
||||
viewChild,
|
||||
} 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';
|
||||
@@ -23,12 +24,13 @@ import buildLucideIconNode from './utils/buildLucideIconNode';
|
||||
template: lucideIconTemplate,
|
||||
host: {
|
||||
...defaultAttributes,
|
||||
class: 'lucide',
|
||||
'[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 {
|
||||
@@ -57,6 +59,16 @@ export abstract class LucideIconBase {
|
||||
readonly size = input(this.iconConfig.size, {
|
||||
transform: (value: Nullable<string | number>) => value ?? this.iconConfig.size,
|
||||
});
|
||||
/**
|
||||
* Icon width.
|
||||
* @default 24
|
||||
*/
|
||||
readonly width = input(null);
|
||||
/**
|
||||
* Icon height.
|
||||
* @default 24
|
||||
*/
|
||||
readonly height = input(null);
|
||||
/**
|
||||
* Stroke color.
|
||||
* @default currentColor
|
||||
@@ -72,36 +84,105 @@ export abstract class LucideIconBase {
|
||||
transform: (value: Nullable<string | number>) => 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<boolean>) => 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<boolean>) => 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(),
|
||||
hasA11yProp: this.title() != null,
|
||||
})
|
||||
: null;
|
||||
});
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
protected readonly computedAttributes = computed<LucideIconNode[1] | undefined>(
|
||||
() => 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',
|
||||
);
|
||||
|
||||
constructor() {
|
||||
effect((onCleanup) => {
|
||||
const icon = this.icon();
|
||||
if (icon) {
|
||||
const absoluteStrokeWidth = this.absoluteStrokeWidth();
|
||||
const [, iconAttributes, iconNode] = buildLucideIconNode(icon as any, {
|
||||
absoluteStrokeWidth,
|
||||
hasA11yProp: this.title() != null,
|
||||
});
|
||||
|
||||
const classes =
|
||||
typeof iconAttributes['class'] === 'string'
|
||||
? iconAttributes['class']
|
||||
.split(' ')
|
||||
.filter((cssClass) => cssClass && cssClass !== 'lucide')
|
||||
: [];
|
||||
|
||||
for (const cssClass of classes) {
|
||||
this.renderer.addClass(this.elRef.nativeElement, cssClass);
|
||||
}
|
||||
|
||||
const iconNode = this.computedIconNode();
|
||||
const iconAttributes = this.computedIconNode();
|
||||
if (iconNode && iconAttributes) {
|
||||
const contentRef = this.contentRef();
|
||||
const refChild = contentRef.nativeElement;
|
||||
const elements = (iconNode ?? []).map(([name, attrs]) => {
|
||||
const elements = iconNode.map(([name, attrs]) => {
|
||||
const element = this.renderer.createElement(name, 'http://www.w3.org/2000/svg');
|
||||
|
||||
Object.entries(attrs).forEach(([name, value]) =>
|
||||
@@ -118,9 +199,6 @@ export abstract class LucideIconBase {
|
||||
elements.forEach((element) =>
|
||||
this.renderer.removeChild(this.elRef.nativeElement, element),
|
||||
);
|
||||
for (const cssClass of classes) {
|
||||
this.renderer.removeClass(this.elRef.nativeElement, cssClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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<Nullable<number | string>>;
|
||||
color: Signal<Nullable<string>>;
|
||||
strokeWidth: Signal<Nullable<number | string>>;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth: Signal<Nullable<boolean>>;
|
||||
nonScalingStroke: Signal<Nullable<boolean>>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
"version": "pnpm version --git-tag-version=false"
|
||||
|
||||
@@ -6,45 +6,39 @@ import { hasA11yProp } from './utils/hasA11yProp';
|
||||
const {
|
||||
color = 'currentColor',
|
||||
size = 24,
|
||||
width,
|
||||
height,
|
||||
'stroke-width': strokeWidth = 2,
|
||||
absoluteStrokeWidth = false,
|
||||
name,
|
||||
aliases,
|
||||
iconNode = [],
|
||||
nonScalingStroke = false,
|
||||
iconNode,
|
||||
icon = {
|
||||
node: iconNode,
|
||||
aliases: [],
|
||||
size: 24,
|
||||
},
|
||||
class: className,
|
||||
...rest
|
||||
} = Astro.props;
|
||||
|
||||
const calculatedStrokeWidth =
|
||||
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth;
|
||||
const [, builtIconAttributes, builtIconNode] = buildLucideIconNode(icon, {
|
||||
color,
|
||||
width: width ?? size,
|
||||
height: height ?? size,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
className,
|
||||
hasA11yProp: hasA11yProp(rest),
|
||||
});
|
||||
|
||||
const [, rawIconAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
name,
|
||||
aliases,
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
color,
|
||||
size,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className,
|
||||
hasA11yProp: hasA11yProp(rest),
|
||||
},
|
||||
);
|
||||
|
||||
const { class: iconClassName, ...builtIconAttributes } = rawIconAttributes;
|
||||
const iconAttributes = {
|
||||
...builtIconAttributes,
|
||||
...rest,
|
||||
};
|
||||
---
|
||||
|
||||
<svg
|
||||
{...iconAttributes}
|
||||
class={iconClassName}
|
||||
>
|
||||
<svg {...iconAttributes}>
|
||||
{builtIconNode.map(([Tag, attrs]) => <Tag {...attrs} />)}
|
||||
<slot />
|
||||
</svg>
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
import type { AstroComponentFactory } from 'astro/runtime/server/render/astro/factory.js';
|
||||
import type { LucideIconData } from './types';
|
||||
import type { LucideIconData, LucideIconNode } from './types';
|
||||
import { render, renderSlot, createComponent, renderComponent } from 'astro/compiler-runtime';
|
||||
import Icon from './Icon.astro';
|
||||
|
||||
export default (iconData: LucideIconData): 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<string, any>, $$slots) => {
|
||||
const { class: className, ...restProps } = $$props;
|
||||
@@ -13,9 +47,7 @@ export default (iconData: LucideIconData): AstroComponentFactory => {
|
||||
Icon,
|
||||
{
|
||||
class: className,
|
||||
name: iconData.name,
|
||||
aliases: iconData.aliases,
|
||||
iconNode: iconData.node,
|
||||
icon,
|
||||
...restProps,
|
||||
},
|
||||
{ default: () => render`${renderSlot($$result, $$slots['default'])}` },
|
||||
@@ -25,4 +57,6 @@ export default (iconData: LucideIconData): AstroComponentFactory => {
|
||||
'none',
|
||||
);
|
||||
return Component;
|
||||
};
|
||||
}
|
||||
|
||||
export default createLucideIcon;
|
||||
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
+29
-13
@@ -1,31 +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) => any;
|
||||
export type AstroComponent = (_props: LucideProps) => any;
|
||||
|
||||
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;
|
||||
name?: string;
|
||||
aliases?: 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<SVGElements, SVGAttributes>;
|
||||
|
||||
export interface LucideIconData {
|
||||
name: string;
|
||||
node: IconNode;
|
||||
aliases?: string[];
|
||||
}
|
||||
export type LucideIconData = SharedLucideIconData<SVGElements, SVGAttributes>;
|
||||
|
||||
type IconNodeChild = [elementName: SVGElements, attrs: SVGAttributes];
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
// All possible svg elements according to the Astro definitions
|
||||
type SVGElements =
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<path d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2">
|
||||
</path>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<path d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2">
|
||||
</path>
|
||||
|
||||
@@ -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-smile"
|
||||
aria-hidden="true"
|
||||
style="position: absolute"
|
||||
class="lucide lucide-smile"
|
||||
>
|
||||
<circle cx="12"
|
||||
cy="12"
|
||||
@@ -46,8 +46,8 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
@@ -77,41 +77,50 @@ exports[`Using lucide icon components > 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"
|
||||
>
|
||||
<path d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z">
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
width="12"
|
||||
height="12"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
<rect vector-effect="non-scaling-stroke"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
@@ -126,8 +135,8 @@ exports[`Using lucide icon components > should pass children to the icon slot 1`
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-smile"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12"
|
||||
cy="12"
|
||||
@@ -164,8 +173,8 @@ exports[`Using lucide icon components > should render a component 1`] = `
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
@@ -195,8 +204,39 @@ exports[`Using lucide icon components > should render the icon with default attr
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { Pen, Edit2, Grid, Droplet, Smile, Rocket } from '../src/lucide-astro';
|
||||
import defaultAttributes from '../src/defaultAttributes';
|
||||
import { Pen, Edit2, Grid, Droplet, Smile, Rocket, type LucideIconData } from '../src/lucide-astro';
|
||||
import defaultAttributes from '../src/utils/defaultAttributes';
|
||||
import { createAstroHTMLString, render } from './utils';
|
||||
import createLucideIcon from '../src/createLucideIcon.ts';
|
||||
|
||||
describe('Using lucide icon components', () => {
|
||||
it('should render a component', async () => {
|
||||
@@ -64,11 +65,11 @@ describe('Using lucide icon components', () => {
|
||||
expect(PenIconContainer.innerHTML).toBe(Edit2Container.innerHTML);
|
||||
});
|
||||
|
||||
it('should not scale the strokeWidth when absoluteStrokeWidth is set', async () => {
|
||||
it('should scale the strokeWidth when absoluteStrokeWidth is set', async () => {
|
||||
const { container } = await render(Grid, {
|
||||
props: {
|
||||
size: 48,
|
||||
stroke: 'red',
|
||||
color: 'red',
|
||||
absoluteStrokeWidth: true,
|
||||
},
|
||||
});
|
||||
@@ -83,6 +84,24 @@ describe('Using lucide icon components', () => {
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply vector-effect when nonScalingStroke is set', async () => {
|
||||
const { container } = await render(Grid, {
|
||||
props: {
|
||||
size: 12,
|
||||
nonScalingStroke: true,
|
||||
},
|
||||
});
|
||||
|
||||
const SVGElement = container.firstElementChild;
|
||||
|
||||
expect(SVGElement).toHaveAttribute('width', '12');
|
||||
expect(SVGElement).toHaveAttribute('height', '12');
|
||||
expect(SVGElement).toHaveAttribute('stroke-width', '2');
|
||||
expect(SVGElement?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should add a non-default attribute to the element', async () => {
|
||||
const { container } = await render(Smile, {
|
||||
props: {
|
||||
@@ -112,7 +131,20 @@ describe('Using lucide icon components', () => {
|
||||
});
|
||||
|
||||
it('should apply all classes to the element', async () => {
|
||||
const { container } = await render(Droplet, {
|
||||
const iconData: LucideIconData = {
|
||||
name: 'droplet',
|
||||
size: 24,
|
||||
node: [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z',
|
||||
},
|
||||
],
|
||||
],
|
||||
aliases: ['drop'],
|
||||
};
|
||||
const { container } = await render(createLucideIcon(iconData), {
|
||||
props: {
|
||||
class: 'my-icon',
|
||||
},
|
||||
@@ -122,6 +154,7 @@ describe('Using lucide icon components', () => {
|
||||
expect(SVGElement).toHaveClass('my-icon');
|
||||
expect(SVGElement).toHaveClass('lucide');
|
||||
expect(SVGElement).toHaveClass('lucide-droplet');
|
||||
expect(SVGElement).toHaveClass('lucide-drop');
|
||||
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -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/icons';
|
||||
const outputFileName = 'lucide-icons';
|
||||
const inputs = [`src/lucide-icons.ts`];
|
||||
@@ -81,7 +86,7 @@ export default [
|
||||
format: 'cjs',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
})),
|
||||
...configs,
|
||||
];
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
import { buildLucideDataUri as sharedBuildLucideDataUri } from '@lucide/shared';
|
||||
import { LucideBuildParams, LucideIconData } from './types';
|
||||
|
||||
/**
|
||||
* Creates a base64 encoded data URI from a Lucide icon object.
|
||||
*
|
||||
* @param icon The icon to build.
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideDataUri(icon: LucideIconData, params: LucideBuildParams = {}) {
|
||||
return sharedBuildLucideDataUri(icon as any, params as any);
|
||||
}
|
||||
|
||||
export default buildLucideDataUri;
|
||||
export { buildLucideDataUri as default } from '@lucide/shared';
|
||||
|
||||
@@ -1,19 +1 @@
|
||||
import { buildLucideIconElement as sharedBuildLucideIconElement } from '@lucide/shared';
|
||||
import { LucideBuildParams, LucideIconData } from './types';
|
||||
|
||||
/**
|
||||
* Creates an SvgElement from a Lucide icon object.
|
||||
*
|
||||
* @param document The document to create the Element in.
|
||||
* @param icon The icon to build.
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideIconElement(
|
||||
document: Document,
|
||||
icon: LucideIconData,
|
||||
params: LucideBuildParams = {},
|
||||
) {
|
||||
return sharedBuildLucideIconElement(document, icon as any, params as any);
|
||||
}
|
||||
|
||||
export default buildLucideIconElement;
|
||||
export { buildLucideIconElement as default } from '@lucide/shared';
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
import { buildLucideIconNode as sharedBuildLucideIconNode } from '@lucide/shared';
|
||||
import { LucideBuildParams, LucideIconData, LucideIconNode } from './types';
|
||||
|
||||
/**
|
||||
* Creates a Lucide icon node (an svgson-like format) from a Lucide icon object.
|
||||
*
|
||||
* @param icon The icon to build.
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideIconNode(icon: LucideIconData, params: LucideBuildParams = {}): LucideIconNode {
|
||||
return sharedBuildLucideIconNode(icon as any, params as any) as LucideIconNode;
|
||||
}
|
||||
|
||||
export default buildLucideIconNode;
|
||||
export { buildLucideIconNode as default } from '@lucide/shared';
|
||||
|
||||
@@ -1,14 +1 @@
|
||||
import { buildLucideSvg as sharedBuildLucideSvg } from '@lucide/shared';
|
||||
import { LucideBuildParams, LucideIconData } from './types';
|
||||
|
||||
/**
|
||||
* Creates an SVG string from a Lucide icon object.
|
||||
*
|
||||
* @param icon The icon to build.
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideSvg(icon: LucideIconData, params: LucideBuildParams = {}) {
|
||||
return sharedBuildLucideSvg(icon as any, params as any);
|
||||
}
|
||||
|
||||
export default buildLucideSvg;
|
||||
export { buildLucideSvg as default } from '@lucide/shared';
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
export type { LucideBuildParams, LucideIconData, LucideIconNode, SVGProps } from '@lucide/shared';
|
||||
import type {
|
||||
LucideBuildParams as SharedLucideBuildParams,
|
||||
LucideIconData as SharedLucideIconData,
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
SVGProps as SharedSvgProps,
|
||||
} from '@lucide/shared/types';
|
||||
export type LucideBuildParams = SharedLucideBuildParams;
|
||||
export type LucideIconData = SharedLucideIconData;
|
||||
export type LucideIconNode = SharedLucideIconNode;
|
||||
export type SVGProps = SharedSvgProps;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`buildLucideIconNode > should match the snapshot 1`] = `"{"xmlns":"http://www.w3.org/2000/svg","width":"24","height":"24","viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","class":"lucide lucide-house lucide-home"}"`;
|
||||
exports[`buildLucideIconNode > should match the snapshot 1`] = `"{"xmlns":"http://www.w3.org/2000/svg","width":24,"height":24,"viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","stroke-width":2,"stroke-linecap":"round","stroke-linejoin":"round","class":"lucide lucide-house lucide-home"}"`;
|
||||
|
||||
@@ -18,24 +18,24 @@ describe('buildLucideIconNode', () => {
|
||||
it('should override dimensions, but not viewBox', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { size: 12 });
|
||||
|
||||
expect(HouseSVG[1]['width']).toBe('12');
|
||||
expect(HouseSVG[1]['height']).toBe('12');
|
||||
expect(HouseSVG[1]['width']).toBe(12);
|
||||
expect(HouseSVG[1]['height']).toBe(12);
|
||||
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
|
||||
});
|
||||
|
||||
it('should override width, but not height', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { width: 12 });
|
||||
|
||||
expect(HouseSVG[1]['width']).toBe('12');
|
||||
expect(HouseSVG[1]['height']).toBe('24');
|
||||
expect(HouseSVG[1]['width']).toBe(12);
|
||||
expect(HouseSVG[1]['height']).toBe(24);
|
||||
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
|
||||
});
|
||||
|
||||
it('should override height, but not width', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { height: 12 });
|
||||
|
||||
expect(HouseSVG[1]['width']).toBe('24');
|
||||
expect(HouseSVG[1]['height']).toBe('12');
|
||||
expect(HouseSVG[1]['width']).toBe(24);
|
||||
expect(HouseSVG[1]['height']).toBe(12);
|
||||
expect(HouseSVG[1]['viewBox']).toBe('0 0 24 24');
|
||||
});
|
||||
|
||||
@@ -49,11 +49,31 @@ describe('buildLucideIconNode', () => {
|
||||
it('should override stroke width', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { strokeWidth: 12 });
|
||||
|
||||
expect(HouseSVG[1]['stroke-width']).toBe('12');
|
||||
expect(HouseSVG[1]['stroke-width']).toBe(12);
|
||||
});
|
||||
|
||||
it('should scale the stroke width', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, {
|
||||
size: 12,
|
||||
strokeWidth: 2,
|
||||
absoluteStrokeWidth: true,
|
||||
});
|
||||
|
||||
expect(HouseSVG[1]['stroke-width']).toBe(4);
|
||||
});
|
||||
|
||||
it('should not scale the stroke width', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, {
|
||||
size: 12,
|
||||
strokeWidth: 2,
|
||||
absoluteStrokeWidth: false,
|
||||
});
|
||||
|
||||
expect(HouseSVG[1]['stroke-width']).toBe(2);
|
||||
});
|
||||
|
||||
it('should set non-scaling-stroke to child nodes', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { absoluteStrokeWidth: true });
|
||||
const HouseSVG = buildLucideIconNode(House, { nonScalingStroke: true });
|
||||
|
||||
for (const node of HouseSVG[2]!) {
|
||||
expect(node[1]['vector-effect']).toBe('non-scaling-stroke');
|
||||
@@ -61,7 +81,7 @@ describe('buildLucideIconNode', () => {
|
||||
});
|
||||
|
||||
it('should not set non-scaling-stroke', () => {
|
||||
const HouseSVG = buildLucideIconNode(House, { absoluteStrokeWidth: false });
|
||||
const HouseSVG = buildLucideIconNode(House, { nonScalingStroke: false });
|
||||
|
||||
expect(HouseSVG[1]['vector-effect']).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@lucide/shared": ["../shared/src/index.ts"],
|
||||
"@lucide/shared/types": ["../shared/src/build/types.ts"],
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
|
||||
@@ -2,6 +2,11 @@ import plugins from '@lucide/rollup-plugins';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import pkg from './package.json' with { type: 'json' };
|
||||
|
||||
const dtsOptions = {
|
||||
includeExternal: ['@lucide/shared/types'],
|
||||
tsconfig: './tsconfig.json',
|
||||
};
|
||||
|
||||
const packageName = 'LucidePreact';
|
||||
const outputFileName = 'lucide-preact';
|
||||
const outputDir = 'dist';
|
||||
@@ -58,7 +63,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.suffixed.ts`,
|
||||
@@ -68,7 +73,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.prefixed.ts`,
|
||||
@@ -78,7 +83,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
...configs,
|
||||
];
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { h, toChildArray } from 'preact';
|
||||
import { buildLucideIconNode, hasA11yProp, mergeClasses } from '@lucide/shared';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import type { LucideIconNode, LucideProps } from './types';
|
||||
import type { LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
import { useLucideContext } from './context';
|
||||
|
||||
interface IconComponentProps extends LucideProps {
|
||||
name?: string;
|
||||
aliases?: string[];
|
||||
iconNode: LucideIconNode;
|
||||
}
|
||||
type IconComponentProps = LucideProps &
|
||||
(
|
||||
| {
|
||||
iconNode: LucideIconNode;
|
||||
icon?: never;
|
||||
}
|
||||
| {
|
||||
icon: LucideIconData;
|
||||
iconNode?: never;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Lucide icon component
|
||||
@@ -19,21 +25,28 @@ interface IconComponentProps extends LucideProps {
|
||||
* @param {number} props.size - The size of the icon
|
||||
* @param {number} props.strokeWidth - The stroke width of the icon
|
||||
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
|
||||
* @param {boolean} props.nonScalingStroke - Whether to use non scaling strokes
|
||||
* @param {string} props.class - The class name of the icon
|
||||
* @param {LucideIconNode} props.children - The children of the icon
|
||||
* @param {LucideIconNode} props.iconNode - The icon node of the icon
|
||||
* @param {LucideIconNode[]} props.children - The children of the icon
|
||||
* @param {LucideIconNode[]} props.iconNode - The icon node of the icon
|
||||
*
|
||||
* @returns {ForwardRefExoticComponent} LucideIcon
|
||||
*/
|
||||
const Icon = ({
|
||||
color,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
children,
|
||||
iconNode,
|
||||
name,
|
||||
aliases,
|
||||
iconNode = [],
|
||||
icon = {
|
||||
node: iconNode,
|
||||
aliases: [],
|
||||
size: 24,
|
||||
},
|
||||
class: classes = '',
|
||||
...rest
|
||||
}: IconComponentProps) => {
|
||||
@@ -41,34 +54,24 @@ const Icon = ({
|
||||
size: contextSize = 24,
|
||||
strokeWidth: contextStrokeWidth = 2,
|
||||
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
|
||||
nonScalingStroke: contextNonScalingStroke = false,
|
||||
color: contextColor = 'currentColor',
|
||||
class: contextClass = '',
|
||||
} = useLucideContext() ?? {};
|
||||
|
||||
const calculatedStrokeWidth =
|
||||
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
|
||||
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
|
||||
: strokeWidth ?? contextStrokeWidth;
|
||||
const [name, svgAttributes, builtIconNode] = buildLucideIconNode(icon, {
|
||||
color: color ?? contextColor,
|
||||
width: width ?? size ?? contextSize,
|
||||
height: height ?? size ?? contextSize,
|
||||
strokeWidth: strokeWidth ?? contextStrokeWidth,
|
||||
absoluteStrokeWidth: absoluteStrokeWidth ?? contextAbsoluteStrokeWidth,
|
||||
nonScalingStroke: nonScalingStroke ?? contextNonScalingStroke,
|
||||
className: mergeClasses(contextClass, classes as string),
|
||||
hasA11yProp: Boolean(children) || hasA11yProp(rest),
|
||||
attributes: rest,
|
||||
});
|
||||
|
||||
const hasAccessibleProp = Boolean(children) || hasA11yProp(rest);
|
||||
|
||||
const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
name,
|
||||
aliases,
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
color: color ?? contextColor,
|
||||
size: size ?? contextSize ?? defaultAttributes.width,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className: mergeClasses(contextClass, classes as string),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
},
|
||||
);
|
||||
|
||||
return h('svg', { ...svgAttributes, ...rest }, [
|
||||
return h(name, { ...svgAttributes }, [
|
||||
...builtIconNode.map(([tag, attrs]) => h(tag, attrs)),
|
||||
...toChildArray(children),
|
||||
]);
|
||||
|
||||
@@ -5,13 +5,18 @@ const LucideContext = createContext<{
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}>({
|
||||
size: 24,
|
||||
color: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
absoluteStrokeWidth: false,
|
||||
nonScalingStroke: false,
|
||||
class: '',
|
||||
});
|
||||
|
||||
@@ -20,7 +25,11 @@ interface LucideProviderProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -30,6 +39,7 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
class: className,
|
||||
}: LucideProviderProps) {
|
||||
const value = useMemo(
|
||||
@@ -38,9 +48,10 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
class: className,
|
||||
}),
|
||||
[size, color, strokeWidth, absoluteStrokeWidth, className],
|
||||
[size, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className],
|
||||
);
|
||||
|
||||
return h(LucideContext.Provider, { value }, children);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { h, type JSX } from 'preact';
|
||||
import { h, JSX } from 'preact';
|
||||
import { mergeClasses, toLucideIconData, toPascalCase } from '@lucide/shared';
|
||||
import Icon from './Icon';
|
||||
import type { LucideIcon, LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
@@ -6,10 +6,10 @@ import type { LucideIcon, LucideIconData, LucideIconNode, LucideProps } from './
|
||||
/**
|
||||
* Creates a Lucide icon component from icon data.
|
||||
*
|
||||
* @param iconData Icon data object.
|
||||
* @param icon Icon data object.
|
||||
* @returns Lucide icon component.
|
||||
*/
|
||||
function createLucideIcon(iconData: LucideIconData): LucideIcon;
|
||||
function createLucideIcon(icon: LucideIconData): LucideIcon;
|
||||
|
||||
/**
|
||||
* Creates a Lucide icon component from the legacy icon arguments.
|
||||
@@ -21,18 +21,18 @@ function createLucideIcon(iconData: LucideIconData): LucideIcon;
|
||||
*/
|
||||
function createLucideIcon(
|
||||
iconName: string,
|
||||
iconNode: LucideIconNode,
|
||||
iconNode: LucideIconNode[],
|
||||
aliases?: string[],
|
||||
): LucideIcon;
|
||||
|
||||
function createLucideIcon(
|
||||
iconDataOrName: LucideIconData | string,
|
||||
iconNode?: LucideIconNode,
|
||||
iconNode?: LucideIconNode[],
|
||||
aliases: string[] = [],
|
||||
): LucideIcon {
|
||||
const iconData =
|
||||
typeof iconDataOrName === 'string'
|
||||
? toLucideIconData(iconDataOrName, iconNode as LucideIconNode, aliases)
|
||||
? toLucideIconData(iconDataOrName, iconNode as LucideIconNode[], aliases)
|
||||
: iconDataOrName;
|
||||
|
||||
const Component = ({ class: classes = '', className = '', children, ...props }: LucideProps) =>
|
||||
@@ -40,15 +40,15 @@ function createLucideIcon(
|
||||
Icon,
|
||||
{
|
||||
...props,
|
||||
name: iconData.name,
|
||||
aliases: iconData.aliases,
|
||||
iconNode: iconData.node,
|
||||
icon: iconData,
|
||||
class: mergeClasses<string | JSX.SignalLike<string | undefined>>(classes, className),
|
||||
},
|
||||
children,
|
||||
);
|
||||
|
||||
Component.displayName = toPascalCase(iconData.name);
|
||||
if (iconData.name) {
|
||||
Component.displayName = toPascalCase(iconData.name);
|
||||
}
|
||||
|
||||
return Component;
|
||||
}
|
||||
|
||||
@@ -1,13 +1 @@
|
||||
import type { JSX } from 'preact';
|
||||
|
||||
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',
|
||||
} satisfies JSX.SVGAttributes;
|
||||
export { defaultAttributes as default } from '@lucide/shared';
|
||||
|
||||
@@ -4,23 +4,26 @@ import type {
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
} from '@lucide/shared';
|
||||
|
||||
export type LucideIconNode = SharedLucideIconNode[];
|
||||
export type LucideIconNode = SharedLucideIconNode;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData & {
|
||||
name: string;
|
||||
node: LucideIconNode;
|
||||
};
|
||||
export type LucideIconData = SharedLucideIconData;
|
||||
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode;
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export interface LucideProps extends Partial<Omit<JSX.SVGAttributes, 'ref' | 'size'>> {
|
||||
color?: string;
|
||||
size?: string | number;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
strokeWidth?: string | number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
}
|
||||
|
||||
export type LucideIcon = FunctionComponent<LucideProps>;
|
||||
|
||||
@@ -31,33 +31,42 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
</svg>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="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"
|
||||
>
|
||||
<rect width="18"
|
||||
<rect vector-effect="non-scaling-stroke"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
@@ -92,3 +101,34 @@ exports[`Using lucide icon components > should render an component 1`] = `
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -51,6 +51,22 @@ describe('Using LucideProvider', () => {
|
||||
expect(IconComponent).toHaveAttribute('stroke-width', '1');
|
||||
});
|
||||
|
||||
it('should render the icon with LucideProvider and custom nonScalingStroke', () => {
|
||||
const { container } = render(
|
||||
<LucideProvider
|
||||
size={48}
|
||||
color="red"
|
||||
nonScalingStroke
|
||||
>
|
||||
<House />
|
||||
</LucideProvider>,
|
||||
);
|
||||
|
||||
const IconComponent = container.firstElementChild;
|
||||
|
||||
expect(IconComponent?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
});
|
||||
|
||||
it("should override the provider's global props when passing props to the icon", () => {
|
||||
const { container } = render(
|
||||
<LucideProvider
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, cleanup } from '@testing-library/preact';
|
||||
import { Pen, Edit2, Grid, Droplet } from '../src/lucide-preact';
|
||||
import { Pen, Edit2, Grid, Droplet, LucideIconData, createLucideIcon } from '../src/lucide-preact';
|
||||
import defaultAttributes from '../src/defaultAttributes';
|
||||
|
||||
type AttributesAssertion = { attributes: Record<string, { value: string }> };
|
||||
@@ -70,7 +70,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(
|
||||
<Grid
|
||||
size={48}
|
||||
@@ -89,12 +89,48 @@ describe('Using lucide icon components', () => {
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply vector-effect when nonScalingStroke is set', () => {
|
||||
const { container } = render(
|
||||
<Grid
|
||||
size={48}
|
||||
stroke="red"
|
||||
nonScalingStroke
|
||||
/>,
|
||||
);
|
||||
|
||||
const SVGElement = container.firstElementChild;
|
||||
|
||||
expect(SVGElement).toHaveAttribute('stroke', 'red');
|
||||
expect(SVGElement).toHaveAttribute('width', '48');
|
||||
expect(SVGElement).toHaveAttribute('height', '48');
|
||||
expect(SVGElement).toHaveAttribute('stroke-width', '2');
|
||||
expect(SVGElement?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply all classes to the element', () => {
|
||||
const iconData: LucideIconData = {
|
||||
name: 'droplet',
|
||||
size: 24,
|
||||
node: [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z',
|
||||
key: 'abc123',
|
||||
},
|
||||
],
|
||||
],
|
||||
aliases: ['drop'],
|
||||
};
|
||||
const testClass = 'my-class';
|
||||
const { container } = render(<Droplet class={testClass} />);
|
||||
const Icon = createLucideIcon(iconData);
|
||||
const { container } = render(<Icon class={testClass} />);
|
||||
|
||||
expect(container.firstChild).toHaveClass(testClass);
|
||||
expect(container.firstChild).toHaveClass('lucide');
|
||||
expect(container.firstChild).toHaveClass('lucide-droplet');
|
||||
expect(container.firstChild).toHaveClass('lucide-drop');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@lucide/shared": ["../shared/src/index.ts"],
|
||||
"@lucide/shared/types": ["../shared/src/build/types.ts"],
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
|
||||
@@ -2,6 +2,11 @@ import plugins from '@lucide/rollup-plugins';
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import pkg from './package.json' with { type: 'json' };
|
||||
|
||||
const dtsOptions = {
|
||||
includeExternal: ['@lucide/shared/types'],
|
||||
tsconfig: './tsconfig.json',
|
||||
};
|
||||
|
||||
const packageName = 'LucideReact';
|
||||
const outputFileName = 'lucide-react-native';
|
||||
const outputDir = 'dist';
|
||||
@@ -61,7 +66,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: inputs[1],
|
||||
@@ -71,7 +76,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.suffixed.ts`,
|
||||
@@ -81,7 +86,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.prefixed.ts`,
|
||||
@@ -91,7 +96,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
...configs,
|
||||
];
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { createElement, forwardRef, type FunctionComponent } from 'react';
|
||||
import * as NativeSvg from 'react-native-svg';
|
||||
import { buildLucideIconNode } from '@lucide/shared';
|
||||
import defaultAttributes, { childDefaultAttributes } from './defaultAttributes';
|
||||
import { LucideIconNode, LucideProps } from './types';
|
||||
import { buildLucideIconForReact, hasA11yProp, mergeClasses } from '@lucide/shared';
|
||||
import { childDefaultAttributes } from './defaultAttributes';
|
||||
import { LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
import { useLucideContext } from './context';
|
||||
|
||||
interface IconComponentProps extends LucideProps {
|
||||
iconNode: LucideIconNode;
|
||||
type IconComponentProps = LucideProps & {
|
||||
testID?: string;
|
||||
className?: string;
|
||||
}
|
||||
} & ({ icon: LucideIconData; iconNode?: never } | { icon?: never; iconNode: LucideIconNode[] });
|
||||
|
||||
const toNativeSvgAttrName = (attributeName: string) => {
|
||||
if (attributeName === 'class') {
|
||||
@@ -44,8 +43,8 @@ const toNativeSvgAttributes = (attributes: Record<string, any>) => {
|
||||
* @param {number} props.strokeWidth - The stroke width of the icon
|
||||
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
|
||||
* @param {string} props.className - The class name of the icon
|
||||
* @param {LucideIconNode} props.children - The children of the icon
|
||||
* @param {LucideIconNode} props.iconNode - The icon node of the icon
|
||||
* @param {LucideIconNode[]} props.children - The children of the icon
|
||||
* @param {LucideIconNode[]} props.iconNode - The icon node of the icon
|
||||
*
|
||||
* @returns {ForwardRefExoticComponent} LucideIcon
|
||||
*/
|
||||
@@ -54,10 +53,17 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
||||
{
|
||||
color,
|
||||
size,
|
||||
width = size,
|
||||
height = size,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
children,
|
||||
iconNode,
|
||||
iconNode = [],
|
||||
icon = {
|
||||
node: iconNode,
|
||||
size: 24,
|
||||
},
|
||||
className,
|
||||
testID,
|
||||
...rest
|
||||
@@ -68,31 +74,28 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
||||
size: contextSize = 24,
|
||||
strokeWidth: contextStrokeWidth = 2,
|
||||
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
|
||||
nonScalingStroke: contextNonScalingStroke = false,
|
||||
color: contextColor = 'currentColor',
|
||||
className: contextClass = '',
|
||||
} = useLucideContext() ?? {};
|
||||
|
||||
const calculatedStrokeWidth =
|
||||
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
|
||||
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
|
||||
: strokeWidth ?? contextStrokeWidth;
|
||||
const hasAccessibleProp = Boolean(children) || hasA11yProp(rest);
|
||||
|
||||
const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
color: color ?? contextColor ?? defaultAttributes.stroke,
|
||||
size: size ?? contextSize ?? defaultAttributes.width,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className,
|
||||
includeDefaultClasses: false,
|
||||
},
|
||||
);
|
||||
const [, svgAttributes, builtIconNode] = buildLucideIconForReact(icon, {
|
||||
color: color ?? contextColor,
|
||||
width: width ?? size ?? contextSize,
|
||||
height: height ?? size ?? contextSize,
|
||||
strokeWidth: strokeWidth ?? contextStrokeWidth,
|
||||
absoluteStrokeWidth: absoluteStrokeWidth ?? contextAbsoluteStrokeWidth,
|
||||
nonScalingStroke: nonScalingStroke ?? contextNonScalingStroke,
|
||||
className: mergeClasses(contextClass, className),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
attributes: rest,
|
||||
});
|
||||
|
||||
const customAttrs = {
|
||||
stroke: color ?? contextColor ?? defaultAttributes.stroke,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
stroke: svgAttributes['stroke'],
|
||||
strokeWidth: svgAttributes['strokeWidth'],
|
||||
...rest,
|
||||
};
|
||||
|
||||
|
||||
@@ -4,12 +4,17 @@ const LucideContext = createContext<{
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
}>({
|
||||
size: 24,
|
||||
color: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
absoluteStrokeWidth: false,
|
||||
nonScalingStroke: false,
|
||||
});
|
||||
|
||||
interface LucideProviderProps {
|
||||
@@ -17,7 +22,11 @@ interface LucideProviderProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
}
|
||||
|
||||
export function LucideProvider({
|
||||
@@ -26,6 +35,7 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
}: LucideProviderProps) {
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
@@ -33,8 +43,9 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
}),
|
||||
[size, color, strokeWidth, absoluteStrokeWidth],
|
||||
[size, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke],
|
||||
);
|
||||
|
||||
return createElement(LucideContext.Provider, { value }, children);
|
||||
|
||||
@@ -6,10 +6,10 @@ import Icon from './Icon';
|
||||
/**
|
||||
* Creates a Lucide icon component from icon data.
|
||||
*
|
||||
* @param iconData Icon data object.
|
||||
* @param icon Icon data object.
|
||||
* @returns Lucide icon component.
|
||||
*/
|
||||
function createLucideIcon(iconData: LucideIconData): LucideIcon;
|
||||
function createLucideIcon(icon: LucideIconData): LucideIcon;
|
||||
|
||||
/**
|
||||
* Creates a Lucide icon component from the legacy icon arguments.
|
||||
@@ -38,7 +38,7 @@ function createLucideIcon(
|
||||
const Component = forwardRef<SVGSVGElement, LucideProps>((props, ref) =>
|
||||
createElement(Icon, {
|
||||
ref,
|
||||
iconNode: iconData.node,
|
||||
icon: iconData,
|
||||
...props,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
const defaultAttributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
};
|
||||
import { defaultReactAttributes } from '@lucide/shared';
|
||||
|
||||
export const childDefaultAttributes = {
|
||||
fill: defaultAttributes.fill,
|
||||
stroke: defaultAttributes.stroke,
|
||||
strokeWidth: defaultAttributes.strokeWidth,
|
||||
strokeLinecap: defaultAttributes.strokeLinecap,
|
||||
strokeLinejoin: defaultAttributes.strokeLinejoin,
|
||||
fill: defaultReactAttributes.fill,
|
||||
stroke: defaultReactAttributes.stroke,
|
||||
strokeWidth: defaultReactAttributes.strokeWidth,
|
||||
strokeLinecap: defaultReactAttributes.strokeLinecap,
|
||||
strokeLinejoin: defaultReactAttributes.strokeLinejoin,
|
||||
};
|
||||
|
||||
export default defaultAttributes;
|
||||
export default defaultReactAttributes;
|
||||
|
||||
@@ -3,23 +3,26 @@ import type { SvgProps } from 'react-native-svg';
|
||||
import type {
|
||||
LucideIconData as SharedLucideIconData,
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
} from '@lucide/shared';
|
||||
} from '@lucide/shared/types';
|
||||
|
||||
export type LucideIconNode = SharedLucideIconNode[];
|
||||
export type LucideIconNode = SharedLucideIconNode;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData & {
|
||||
name: string;
|
||||
node: LucideIconNode;
|
||||
};
|
||||
export type LucideIconData = SharedLucideIconData;
|
||||
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode;
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export interface LucideProps extends SvgProps {
|
||||
size?: string | number;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import { Icon } from '../src/lucide-react-native';
|
||||
vi.mock('react-native-svg');
|
||||
|
||||
describe('Using Icon Component', () => {
|
||||
const airVentIcon = { name: 'air-vent', node: airVent };
|
||||
|
||||
it('should render icon based on a iconNode', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
@@ -32,4 +34,30 @@ describe('Using Icon Component', () => {
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render icon based on icon data', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
icon={airVentIcon}
|
||||
size={48}
|
||||
stroke="red"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.firstChild).toBeDefined();
|
||||
});
|
||||
|
||||
it('should support nonScalingStroke', async () => {
|
||||
const { container } = render(
|
||||
<Icon
|
||||
icon={airVentIcon}
|
||||
size={48}
|
||||
stroke="red"
|
||||
strokeWidth={2}
|
||||
nonScalingStroke
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container.firstChild?.firstChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`Using LucideProvider > should render the icon with LucideProvider 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-house lucide-home"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
|
||||
+139
-58
@@ -2,6 +2,8 @@
|
||||
|
||||
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
data-testid="grid-icon"
|
||||
fill="none"
|
||||
height="48"
|
||||
@@ -60,6 +62,71 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should apply vector-effect when nonScalingStroke is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
vector-effect="non-scaling-stroke"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should duplicate properties to children components 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
@@ -70,6 +137,8 @@ exports[`Using lucide icon components > should duplicate properties to children
|
||||
stroke-width="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
data-testid="multiple-children"
|
||||
>
|
||||
<rect fill="red"
|
||||
@@ -119,64 +188,6 @@ exports[`Using lucide icon components > should duplicate properties to children
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should render an component 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
@@ -187,6 +198,8 @@ exports[`Using lucide icon components > should render an 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"
|
||||
>
|
||||
<rect fill="none"
|
||||
stroke="currentColor"
|
||||
@@ -235,6 +248,66 @@ exports[`Using lucide icon components > should render an component 1`] = `
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should work with a single child component 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
@@ -245,6 +318,7 @@ exports[`Using lucide icon components > should work with a single child componen
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
data-testid="single-child"
|
||||
>
|
||||
<rect fill="none"
|
||||
@@ -300,6 +374,8 @@ exports[`Using lucide icon components > should work with a single child componen
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
data-testid="child"
|
||||
>
|
||||
<rect fill="none"
|
||||
@@ -360,6 +436,7 @@ exports[`Using lucide icon components > should work with several children compon
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
data-testid="multiple-children"
|
||||
>
|
||||
<rect fill="none"
|
||||
@@ -415,6 +492,8 @@ exports[`Using lucide icon components > should work with several children compon
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
data-testid="child1"
|
||||
>
|
||||
<rect fill="none"
|
||||
@@ -471,6 +550,8 @@ exports[`Using lucide icon components > should work with several children compon
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
data-testid="child2"
|
||||
>
|
||||
<rect fill="none"
|
||||
|
||||
@@ -63,7 +63,7 @@ describe('Using lucide icon components', () => {
|
||||
expect(penIcon).toStrictEqual(edit2Icon);
|
||||
});
|
||||
|
||||
it('should not scale the strokeWidth when absoluteStrokeWidth is set', () => {
|
||||
it('should scale the strokeWidth when absoluteStrokeWidth is set', () => {
|
||||
const { container } = render(
|
||||
<Grid
|
||||
size={48}
|
||||
@@ -82,6 +82,26 @@ describe('Using lucide icon components', () => {
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply vector-effect when nonScalingStroke is set', () => {
|
||||
const { container } = render(
|
||||
<Grid
|
||||
size={48}
|
||||
stroke="red"
|
||||
nonScalingStroke
|
||||
/>,
|
||||
);
|
||||
|
||||
const SVGElement = container.firstElementChild;
|
||||
|
||||
expect(SVGElement).toHaveAttribute('stroke', 'red');
|
||||
expect(SVGElement).toHaveAttribute('width', '48');
|
||||
expect(SVGElement).toHaveAttribute('height', '48');
|
||||
expect(SVGElement).toHaveAttribute('stroke-width', '2');
|
||||
expect(SVGElement?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should work with a single child component', () => {
|
||||
const testId = 'single-child';
|
||||
const childId = 'child';
|
||||
|
||||
@@ -9,6 +9,11 @@
|
||||
"isolatedModules": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@lucide/shared": ["../shared/src/index.ts"],
|
||||
"@lucide/shared/types": ["../shared/src/build/types.ts"],
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"@lucide/shared": "workspace:*",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^18.2.37",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"jest-serializer-html": "^7.1.0",
|
||||
|
||||
@@ -4,6 +4,11 @@ import pkg from './package.json' with { type: 'json' };
|
||||
import dts from 'rollup-plugin-dts';
|
||||
import getAliasesEntryNames from './scripts/getAliasesEntryNames.mts';
|
||||
|
||||
const dtsOptions = {
|
||||
includeExternal: ['@lucide/shared/types'],
|
||||
tsconfig: './tsconfig.json',
|
||||
};
|
||||
|
||||
const aliasesEntries = await getAliasesEntryNames();
|
||||
|
||||
const packageName = 'LucideReact';
|
||||
@@ -117,7 +122,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: 'src/dynamic.ts',
|
||||
@@ -132,7 +137,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: inputs[0],
|
||||
@@ -142,7 +147,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.suffixed.ts`,
|
||||
@@ -152,7 +157,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
{
|
||||
input: `src/${outputFileName}.prefixed.ts`,
|
||||
@@ -162,7 +167,7 @@ export default [
|
||||
format: 'es',
|
||||
},
|
||||
],
|
||||
plugins: [dts()],
|
||||
plugins: [dts(dtsOptions)],
|
||||
},
|
||||
...configs,
|
||||
];
|
||||
|
||||
@@ -8,10 +8,15 @@ export default defineExportTemplate(
|
||||
|
||||
return `
|
||||
import createLucideIcon from '../createLucideIcon';
|
||||
import type { LucideIconData } from '../types';
|
||||
import type { LucideIconNode, LucideIconData } from '../types';
|
||||
|
||||
export const __iconData: LucideIconData = ${JSON.stringify(iconData)}
|
||||
|
||||
/**
|
||||
* @deprecated Access \`__iconData\` instead.
|
||||
*/
|
||||
export const __iconNode: LucideIconNode[] = __iconData.node;
|
||||
|
||||
/**
|
||||
* @component @name ${componentName}
|
||||
* @description Lucide SVG icon component, renders SVG Element with children.
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { createElement, forwardRef, useEffect, useState } from 'react';
|
||||
import { LucideIcon, LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
import { LucideIcon, LucideIconData, LucideProps } from './types';
|
||||
import dynamicIconImports from './dynamicIconImports';
|
||||
import Icon from './Icon';
|
||||
|
||||
export type DynamicIconModule = {
|
||||
default: LucideIcon;
|
||||
__iconNode?: LucideIconNode;
|
||||
__iconData?: LucideIconData;
|
||||
};
|
||||
|
||||
@@ -20,7 +19,7 @@ interface DynamicIconComponentProps extends LucideProps {
|
||||
fallback?: () => JSX.Element | null;
|
||||
}
|
||||
|
||||
async function getIconNode(name: IconName) {
|
||||
async function getIconData(name: IconName) {
|
||||
if (!(name in dynamicIconImports)) {
|
||||
throw new Error('[lucide-react]: Name in Lucide DynamicIcon not found');
|
||||
}
|
||||
@@ -29,11 +28,7 @@ async function getIconNode(name: IconName) {
|
||||
const icon = (await dynamicIconImports[name]()) as DynamicIconModule;
|
||||
|
||||
if (icon.__iconData != null) {
|
||||
return icon.__iconData.node;
|
||||
}
|
||||
|
||||
if (icon.__iconNode != null) {
|
||||
return icon.__iconNode;
|
||||
return icon.__iconData;
|
||||
}
|
||||
|
||||
throw new Error('[lucide-react]: Failed to resolve icon data for DynamicIcon');
|
||||
@@ -48,25 +43,26 @@ async function getIconNode(name: IconName) {
|
||||
* @param {number} props.size - The size of the icon
|
||||
* @param {number} props.strokeWidth - The stroke width of the icon
|
||||
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
|
||||
* @param {boolean} props.nonScalingStroke - Whether to use non scaling strokes
|
||||
* @param {string} props.className - The class name of the icon
|
||||
* @param {LucideIconNode} props.children - The children of the icon
|
||||
* @param {LucideIconNode} props.iconNode - The icon node of the icon
|
||||
* @param {LucideIconNode[]} props.children - The children of the icon
|
||||
* @param {LucideIconNode[]} props.iconNode - The icon node of the icon
|
||||
*
|
||||
* @returns {ForwardRefExoticComponent} LucideIcon
|
||||
*/
|
||||
const DynamicIcon = forwardRef<SVGSVGElement, DynamicIconComponentProps>(
|
||||
({ name, fallback: Fallback, ...props }, ref) => {
|
||||
const [iconNode, setIconNode] = useState<LucideIconNode>();
|
||||
const [iconData, setIconData] = useState<LucideIconData>();
|
||||
|
||||
useEffect(() => {
|
||||
getIconNode(name)
|
||||
.then(setIconNode)
|
||||
getIconData(name)
|
||||
.then(setIconData)
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}, [name]);
|
||||
|
||||
if (iconNode == null) {
|
||||
if (iconData == null) {
|
||||
if (Fallback == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -77,7 +73,7 @@ const DynamicIcon = forwardRef<SVGSVGElement, DynamicIconComponentProps>(
|
||||
return createElement(Icon, {
|
||||
ref,
|
||||
...props,
|
||||
iconNode,
|
||||
icon: iconData,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,39 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { createElement, forwardRef } from 'react';
|
||||
import { buildLucideIconNode, hasA11yProp, mergeClasses } from '@lucide/shared';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { LucideIconNode, LucideProps } from './types';
|
||||
import { buildLucideIconForReact, hasA11yProp, mergeClasses } from '@lucide/shared';
|
||||
import { LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
import { useLucideContext } from './context';
|
||||
|
||||
interface IconComponentProps extends LucideProps {
|
||||
name?: string;
|
||||
aliases?: string[];
|
||||
iconNode: LucideIconNode;
|
||||
}
|
||||
|
||||
const toReactAttributeName = (attributeName: string) => {
|
||||
if (attributeName === 'class') {
|
||||
return 'className';
|
||||
}
|
||||
|
||||
if (attributeName.startsWith('data-') || attributeName.startsWith('aria-')) {
|
||||
return attributeName;
|
||||
}
|
||||
|
||||
return attributeName.replace(/-([a-z])/g, (_, character: string) => character.toUpperCase());
|
||||
};
|
||||
|
||||
const toReactAttributes = (attributes: Record<string, any>) => {
|
||||
return Object.entries(attributes).reduce<Record<string, any>>((acc, [attributeName, value]) => {
|
||||
if (value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[toReactAttributeName(attributeName)] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
type IconComponentProps = LucideProps &
|
||||
(
|
||||
| {
|
||||
iconNode: LucideIconNode[];
|
||||
icon?: never;
|
||||
}
|
||||
| {
|
||||
icon: LucideIconData;
|
||||
iconNode?: never;
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Lucide icon component
|
||||
@@ -44,9 +26,10 @@ const toReactAttributes = (attributes: Record<string, any>) => {
|
||||
* @param {number} props.size - The size of the icon
|
||||
* @param {number} props.strokeWidth - The stroke width of the icon
|
||||
* @param {boolean} props.absoluteStrokeWidth - Whether to use absolute stroke width
|
||||
* @param {boolean} props.nonScalingStroke - Whether to use non scaling strokes
|
||||
* @param {string} props.className - The class name of the icon
|
||||
* @param {LucideIconNode} props.children - The children of the icon
|
||||
* @param {LucideIconNode} props.iconNode - The icon node of the icon
|
||||
* @param {LucideIconNode[]} props.children - The children of the icon
|
||||
* @param {LucideIconNode[]} props.iconNode - The icon node of the icon
|
||||
*
|
||||
* @returns {ForwardRefExoticComponent} LucideIcon
|
||||
*/
|
||||
@@ -55,13 +38,19 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
||||
{
|
||||
color,
|
||||
size,
|
||||
width,
|
||||
height,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
className = '',
|
||||
children,
|
||||
iconNode,
|
||||
name,
|
||||
aliases,
|
||||
iconNode = [],
|
||||
icon = {
|
||||
node: iconNode,
|
||||
aliases: [],
|
||||
size: 24,
|
||||
},
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
@@ -70,39 +59,30 @@ const Icon = forwardRef<SVGSVGElement, IconComponentProps>(
|
||||
size: contextSize = 24,
|
||||
strokeWidth: contextStrokeWidth = 2,
|
||||
absoluteStrokeWidth: contextAbsoluteStrokeWidth = false,
|
||||
nonScalingStroke: contextNonScalingStroke = false,
|
||||
color: contextColor = 'currentColor',
|
||||
className: contextClass = '',
|
||||
} = useLucideContext() ?? {};
|
||||
|
||||
const calculatedStrokeWidth =
|
||||
absoluteStrokeWidth ?? contextAbsoluteStrokeWidth
|
||||
? (Number(strokeWidth ?? contextStrokeWidth) * 24) / Number(size ?? contextSize)
|
||||
: strokeWidth ?? contextStrokeWidth;
|
||||
|
||||
const hasAccessibleProp = Boolean(children) || hasA11yProp(rest);
|
||||
|
||||
const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
name,
|
||||
aliases,
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
color: color ?? contextColor,
|
||||
size: size ?? contextSize ?? defaultAttributes.width,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className: mergeClasses(contextClass, className),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
},
|
||||
);
|
||||
const [name, svgAttributes, builtIconNode] = buildLucideIconForReact(icon, {
|
||||
color: color ?? contextColor,
|
||||
width: width ?? size ?? contextSize,
|
||||
height: height ?? size ?? contextSize,
|
||||
strokeWidth: strokeWidth ?? contextStrokeWidth,
|
||||
absoluteStrokeWidth: absoluteStrokeWidth ?? contextAbsoluteStrokeWidth,
|
||||
nonScalingStroke: nonScalingStroke ?? contextNonScalingStroke,
|
||||
className: mergeClasses(contextClass, className),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
attributes: rest,
|
||||
});
|
||||
|
||||
return createElement(
|
||||
'svg',
|
||||
name,
|
||||
{
|
||||
ref,
|
||||
...toReactAttributes(svgAttributes),
|
||||
...rest,
|
||||
...svgAttributes,
|
||||
},
|
||||
[
|
||||
...builtIconNode.map(([tag, attrs]) => createElement(tag, attrs)),
|
||||
|
||||
@@ -7,7 +7,11 @@ type LucideConfig = {
|
||||
size: number;
|
||||
color: string;
|
||||
strokeWidth: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth: boolean;
|
||||
nonScalingStroke: boolean;
|
||||
className: string;
|
||||
};
|
||||
|
||||
@@ -23,6 +27,7 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
className,
|
||||
}: LucideProviderProps) {
|
||||
const value = useMemo(
|
||||
@@ -31,9 +36,10 @@ export function LucideProvider({
|
||||
color,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
className,
|
||||
}),
|
||||
[size, color, strokeWidth, absoluteStrokeWidth, className],
|
||||
[size, color, strokeWidth, absoluteStrokeWidth, nonScalingStroke, className],
|
||||
);
|
||||
|
||||
return createElement(LucideContext.Provider, { value }, children);
|
||||
|
||||
@@ -6,10 +6,10 @@ import Icon from './Icon';
|
||||
/**
|
||||
* Creates a Lucide icon component from icon data.
|
||||
*
|
||||
* @param iconData Icon data object.
|
||||
* @param icon Icon data object.
|
||||
* @returns Lucide icon component.
|
||||
*/
|
||||
function createLucideIcon(iconData: LucideIconData): LucideIcon;
|
||||
function createLucideIcon(icon: LucideIconData): LucideIcon;
|
||||
|
||||
/**
|
||||
* Creates a Lucide icon component from the legacy icon arguments.
|
||||
@@ -21,32 +21,32 @@ function createLucideIcon(iconData: LucideIconData): LucideIcon;
|
||||
*/
|
||||
function createLucideIcon(
|
||||
iconName: string,
|
||||
iconNode: LucideIconNode,
|
||||
iconNode: LucideIconNode[],
|
||||
aliases?: string[],
|
||||
): LucideIcon;
|
||||
|
||||
function createLucideIcon(
|
||||
iconDataOrName: LucideIconData | string,
|
||||
iconNode?: LucideIconNode,
|
||||
iconNode: LucideIconNode[] = [],
|
||||
aliases: string[] = [],
|
||||
): LucideIcon {
|
||||
const iconData =
|
||||
typeof iconDataOrName === 'string'
|
||||
? toLucideIconData(iconDataOrName, iconNode as LucideIconNode, aliases)
|
||||
? toLucideIconData(iconDataOrName, iconNode, aliases)
|
||||
: iconDataOrName;
|
||||
|
||||
const Component = forwardRef<SVGSVGElement, LucideProps>(({ className, ...props }, ref) =>
|
||||
createElement(Icon, {
|
||||
ref,
|
||||
name: iconData.name,
|
||||
aliases: iconData.aliases,
|
||||
iconNode: iconData.node,
|
||||
icon: iconData,
|
||||
className,
|
||||
...props,
|
||||
}),
|
||||
);
|
||||
|
||||
Component.displayName = toPascalCase(iconData.name);
|
||||
if (iconData.name) {
|
||||
Component.displayName = toPascalCase(iconData.name);
|
||||
}
|
||||
|
||||
return Component;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
};
|
||||
export { defaultReactAttributes as default } from '@lucide/shared';
|
||||
|
||||
@@ -2,26 +2,28 @@ import type { SVGProps, ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||
import type {
|
||||
LucideIconData as SharedLucideIconData,
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
} from '@lucide/shared';
|
||||
} from '@lucide/shared/types';
|
||||
|
||||
export type LucideIconNode = SharedLucideIconNode[];
|
||||
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData & {
|
||||
name: string;
|
||||
node: LucideIconNode;
|
||||
};
|
||||
export type LucideIconNode = SharedLucideIconNode<string, SVGAttributes>;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData<string, SVGAttributes>;
|
||||
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode;
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>;
|
||||
type ElementAttributes = RefAttributes<SVGSVGElement> & SVGAttributes;
|
||||
|
||||
export interface LucideProps extends ElementAttributes {
|
||||
size?: string | number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
}
|
||||
|
||||
export type LucideIcon = ForwardRefExoticComponent<
|
||||
|
||||
@@ -22,3 +22,51 @@ exports[`Using LucideProvider > should render the icon with LucideProvider 1`] =
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using LucideProvider > should render the icon with LucideProvider and custom absoluteStrokeWidth 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-house lucide-home"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
|
||||
/>
|
||||
<path
|
||||
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using LucideProvider > should render the icon with LucideProvider and custom nonScalingStroke 1`] = `
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="lucide lucide-house lucide-home"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
<path
|
||||
d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -31,33 +31,42 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
</svg>
|
||||
`;
|
||||
|
||||
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`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="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"
|
||||
>
|
||||
<rect width="18"
|
||||
<rect vector-effect="non-scaling-stroke"
|
||||
width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 9h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M3 15h18"
|
||||
>
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M9 3v18"
|
||||
>
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
<path vector-effect="non-scaling-stroke"
|
||||
d="M15 3v18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
@@ -92,3 +101,34 @@ exports[`Using lucide icon components > should render an component 1`] = `
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18">
|
||||
</path>
|
||||
<path d="M3 15h18">
|
||||
</path>
|
||||
<path d="M9 3v18">
|
||||
</path>
|
||||
<path d="M15 3v18">
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -60,6 +60,24 @@ describe('Using LucideProvider', () => {
|
||||
const IconComponent = container.firstElementChild;
|
||||
|
||||
expect(IconComponent).toHaveAttribute('stroke-width', '1');
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render the icon with LucideProvider and custom nonScalingStroke', () => {
|
||||
const { container } = render(
|
||||
<LucideProvider
|
||||
size={48}
|
||||
color="red"
|
||||
nonScalingStroke
|
||||
>
|
||||
<House />
|
||||
</LucideProvider>,
|
||||
);
|
||||
|
||||
const IconComponent = container.firstElementChild;
|
||||
|
||||
expect(IconComponent?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should override the provider's global props when passing props to the icon", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import { Pen, Edit2, Grid, Droplet } from '../src/lucide-react';
|
||||
import { Pen, Edit2, Grid, createLucideIcon, LucideIconData } from '../src/lucide-react';
|
||||
import defaultAttributes from '../src/defaultAttributes';
|
||||
|
||||
describe('Using lucide icon components', () => {
|
||||
@@ -69,7 +69,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, getByTestId } = render(
|
||||
<Grid
|
||||
size={48}
|
||||
@@ -88,12 +88,48 @@ describe('Using lucide icon components', () => {
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply vector-effect when nonScalingStroke is set', () => {
|
||||
const { container, getByTestId } = render(
|
||||
<Grid
|
||||
size={48}
|
||||
stroke="red"
|
||||
nonScalingStroke
|
||||
/>,
|
||||
);
|
||||
|
||||
const SVGElement = container.firstElementChild;
|
||||
|
||||
expect(SVGElement).toHaveAttribute('stroke', 'red');
|
||||
expect(SVGElement).toHaveAttribute('width', '48');
|
||||
expect(SVGElement).toHaveAttribute('height', '48');
|
||||
expect(SVGElement).toHaveAttribute('stroke-width', '2');
|
||||
expect(SVGElement?.firstElementChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
|
||||
expect(container.innerHTML).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should apply all classNames to the element', () => {
|
||||
const iconData: LucideIconData = {
|
||||
name: 'droplet',
|
||||
size: 24,
|
||||
node: [
|
||||
[
|
||||
'path',
|
||||
{
|
||||
d: 'M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5C6 11.1 5 13 5 15a7 7 0 0 0 7 7z',
|
||||
key: 'abc123',
|
||||
},
|
||||
],
|
||||
],
|
||||
aliases: ['drop'],
|
||||
};
|
||||
const testClass = 'my-class';
|
||||
const { container } = render(<Droplet className={testClass} />);
|
||||
const Icon = createLucideIcon(iconData);
|
||||
const { container } = render(<Icon className={testClass} />);
|
||||
|
||||
expect(container.firstChild).toHaveClass(testClass);
|
||||
expect(container.firstChild).toHaveClass('lucide');
|
||||
expect(container.firstChild).toHaveClass('lucide-droplet');
|
||||
expect(container.firstChild).toHaveClass('lucide-drop');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@lucide/shared": ["../shared/src/index.ts"],
|
||||
"@lucide/shared/types": ["../shared/src/build/types.ts"],
|
||||
},
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["esnext", "dom"],
|
||||
@@ -19,7 +24,7 @@
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"jsx": "react-jsx",
|
||||
"types": ["@testing-library/jest-dom"],
|
||||
"types": ["@testing-library/jest-dom", "node"],
|
||||
},
|
||||
"exclude": ["**/node_modules"],
|
||||
"include": ["src", "tests"],
|
||||
|
||||
@@ -24,7 +24,7 @@ const iconData: LucideIconData = ${JSON.stringify(iconData)};
|
||||
* ${deprecated ? `@deprecated ${deprecationReason}` : ''}
|
||||
*/
|
||||
const ${componentName} = (props: LucideProps) => (
|
||||
<Icon {...props} iconNode={iconData.node} name={iconData.name} aliases={iconData.aliases} />
|
||||
<Icon {...props} icon={iconData} />
|
||||
)
|
||||
|
||||
export default ${componentName};
|
||||
|
||||
@@ -1,77 +1,58 @@
|
||||
import { For, splitProps, useContext } from 'solid-js';
|
||||
import { Dynamic } from 'solid-js/web';
|
||||
import { buildLucideIconNode, hasA11yProp, mergeClasses } from '@lucide/shared';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { IconNode, LucideProps } from './types';
|
||||
import { LucideIconData, LucideIconNode, LucideProps } from './types';
|
||||
import { LucideContext } from './context';
|
||||
|
||||
interface IconProps {
|
||||
name?: string;
|
||||
aliases?: string[];
|
||||
iconNode: IconNode;
|
||||
}
|
||||
type IconProps =
|
||||
| {
|
||||
icon: LucideIconData;
|
||||
iconNode?: never;
|
||||
}
|
||||
| {
|
||||
icon?: never;
|
||||
iconNode?: LucideIconNode[];
|
||||
};
|
||||
|
||||
const Icon = (props: LucideProps & IconProps) => {
|
||||
const [localProps, rest] = splitProps(props, [
|
||||
'color',
|
||||
'size',
|
||||
'width',
|
||||
'height',
|
||||
'strokeWidth',
|
||||
'children',
|
||||
'class',
|
||||
'name',
|
||||
'aliases',
|
||||
'icon',
|
||||
'iconNode',
|
||||
'absoluteStrokeWidth',
|
||||
'nonScalingStroke',
|
||||
]);
|
||||
|
||||
const globalProps = useContext(LucideContext);
|
||||
|
||||
const calculatedStrokeWidth =
|
||||
(localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth) === true
|
||||
? (Number(
|
||||
localProps.strokeWidth ?? globalProps.strokeWidth ?? defaultAttributes['stroke-width'],
|
||||
) *
|
||||
24) /
|
||||
Number(localProps.size ?? globalProps.size)
|
||||
: Number(
|
||||
localProps.strokeWidth ?? globalProps.strokeWidth ?? defaultAttributes['stroke-width'],
|
||||
);
|
||||
|
||||
const hasAccessibleProp = Boolean(localProps.children) || hasA11yProp(rest);
|
||||
|
||||
const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
name: localProps.name,
|
||||
aliases: localProps.aliases,
|
||||
size: 24,
|
||||
node: localProps.iconNode,
|
||||
},
|
||||
{
|
||||
color: localProps.color ?? globalProps.color ?? defaultAttributes.stroke,
|
||||
size: localProps.size ?? globalProps.size ?? defaultAttributes.width,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className: mergeClasses('lucide-icon', globalProps.class, localProps.class),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
},
|
||||
);
|
||||
|
||||
const svgElementAttributes = {
|
||||
xmlns: svgAttributes.xmlns,
|
||||
viewBox: svgAttributes.viewBox,
|
||||
fill: svgAttributes.fill,
|
||||
'stroke-linecap': svgAttributes['stroke-linecap'],
|
||||
'stroke-linejoin': svgAttributes['stroke-linejoin'],
|
||||
width: svgAttributes.width,
|
||||
height: svgAttributes.height,
|
||||
stroke: svgAttributes.stroke,
|
||||
'stroke-width': svgAttributes['stroke-width'],
|
||||
class: svgAttributes.class,
|
||||
'aria-hidden': svgAttributes['aria-hidden'],
|
||||
...rest,
|
||||
const icon: LucideIconData = localProps.icon ?? {
|
||||
node: localProps.iconNode ?? ([] as LucideIconNode[]),
|
||||
size: 24,
|
||||
aliases: [],
|
||||
};
|
||||
|
||||
const [, svgAttributes, builtIconNode] = buildLucideIconNode(icon, {
|
||||
color: localProps.color ?? globalProps.color,
|
||||
width: localProps.width ?? localProps.size ?? globalProps.size,
|
||||
height: localProps.height ?? localProps.size ?? globalProps.size,
|
||||
strokeWidth: localProps.strokeWidth ?? globalProps.strokeWidth,
|
||||
absoluteStrokeWidth: localProps.absoluteStrokeWidth ?? globalProps.absoluteStrokeWidth,
|
||||
nonScalingStroke: localProps.nonScalingStroke ?? globalProps.nonScalingStroke,
|
||||
className: mergeClasses('lucide-icon', globalProps.class, localProps.class),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
attributes: rest,
|
||||
});
|
||||
|
||||
return (
|
||||
<svg {...svgElementAttributes}>
|
||||
<svg {...svgAttributes}>
|
||||
<For each={builtIconNode}>
|
||||
{([elementName, attrs]) => {
|
||||
return (
|
||||
|
||||
@@ -4,13 +4,18 @@ export const LucideContext = createContext<{
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}>({
|
||||
size: 24,
|
||||
color: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
absoluteStrokeWidth: false,
|
||||
nonScalingStroke: false,
|
||||
class: '',
|
||||
});
|
||||
|
||||
@@ -19,7 +24,11 @@ interface LucideProviderProps {
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -29,6 +38,7 @@ export function LucideProvider(props: LucideProviderProps) {
|
||||
'color',
|
||||
'strokeWidth',
|
||||
'absoluteStrokeWidth',
|
||||
'nonScalingStroke',
|
||||
'class',
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,16 +1 @@
|
||||
import { JSX } from 'solid-js/jsx-runtime';
|
||||
import { 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;
|
||||
export { defaultAttributes as default } from '@lucide/shared';
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
import { JSX } from 'solid-js/jsx-runtime';
|
||||
import type {
|
||||
LucideIconData as SharedLucideIconData,
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
} from '@lucide/shared/types';
|
||||
|
||||
export type IconNode = [elementName: keyof JSX.IntrinsicElements, attrs: Record<string, string>][];
|
||||
export interface LucideIconData {
|
||||
name: string;
|
||||
node: IconNode;
|
||||
aliases?: string[];
|
||||
}
|
||||
export type SVGAttributes = Partial<JSX.SvgSVGAttributes<SVGSVGElement>>;
|
||||
|
||||
export type LucideIconNode = SharedLucideIconNode<keyof JSX.IntrinsicElements, SVGAttributes>;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData<keyof JSX.IntrinsicElements, SVGAttributes>;
|
||||
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export interface LucideProps extends SVGAttributes {
|
||||
key?: string | number;
|
||||
class?: string;
|
||||
size?: string | number;
|
||||
width?: string | number;
|
||||
height?: string | number;
|
||||
color?: string;
|
||||
strokeWidth?: string | number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
}
|
||||
|
||||
export type LucideIcon = (props: LucideProps) => JSX.Element;
|
||||
|
||||
@@ -5,6 +5,8 @@ import { airVent } from './testIconNodes';
|
||||
import { Icon } from '../src/lucide-solid';
|
||||
|
||||
describe('Using Icon Component', () => {
|
||||
const airVentIcon = { name: 'air-vent', node: airVent };
|
||||
|
||||
it('should render icon based on a iconNode', async () => {
|
||||
const { container } = render(() => (
|
||||
<Icon
|
||||
@@ -30,6 +32,32 @@ describe('Using Icon Component', () => {
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render icon based on icon data', async () => {
|
||||
const { container } = render(() => (
|
||||
<Icon
|
||||
icon={airVentIcon}
|
||||
size={48}
|
||||
stroke="red"
|
||||
/>
|
||||
));
|
||||
|
||||
expect(container.firstChild).toBeDefined();
|
||||
});
|
||||
|
||||
it('should support nonScalingStroke', async () => {
|
||||
const { container } = render(() => (
|
||||
<Icon
|
||||
icon={airVentIcon}
|
||||
size={48}
|
||||
stroke="red"
|
||||
strokeWidth={2}
|
||||
nonScalingStroke
|
||||
/>
|
||||
));
|
||||
|
||||
expect(container.firstChild?.firstChild).toHaveAttribute('vector-effect', 'non-scaling-stroke');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Icon Component Accessibility', () => {
|
||||
|
||||
@@ -2,55 +2,14 @@
|
||||
|
||||
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="4"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3 lucide-icon"
|
||||
aria-hidden="true"
|
||||
data-testid="grid-icon"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
key="afitv7"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18"
|
||||
key="1pudct"
|
||||
>
|
||||
</path>
|
||||
<path d="M3 15h18"
|
||||
key="5xshup"
|
||||
>
|
||||
</path>
|
||||
<path d="M9 3v18"
|
||||
key="fh3hqa"
|
||||
>
|
||||
</path>
|
||||
<path d="M15 3v18"
|
||||
key="14nvp0"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
width="48"
|
||||
height="48"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3 lucide-icon"
|
||||
aria-hidden="true"
|
||||
data-testid="grid-icon"
|
||||
@@ -84,14 +43,14 @@ exports[`Using lucide icon components > should not scale the strokeWidth when ab
|
||||
|
||||
exports[`Using lucide icon components > should render a component 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
width="24"
|
||||
height="24"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3 lucide-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -121,3 +80,44 @@ exports[`Using lucide icon components > should render a component 1`] = `
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-grid-3x3 lucide-grid lucide-grid-3-x-3 lucide-icon"
|
||||
aria-hidden="true"
|
||||
data-testid="grid-icon"
|
||||
>
|
||||
<rect width="18"
|
||||
height="18"
|
||||
x="3"
|
||||
y="3"
|
||||
rx="2"
|
||||
key="afitv7"
|
||||
>
|
||||
</rect>
|
||||
<path d="M3 9h18"
|
||||
key="1pudct"
|
||||
>
|
||||
</path>
|
||||
<path d="M3 15h18"
|
||||
key="5xshup"
|
||||
>
|
||||
</path>
|
||||
<path d="M9 3v18"
|
||||
key="fh3hqa"
|
||||
>
|
||||
</path>
|
||||
<path d="M15 3v18"
|
||||
key="14nvp0"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -57,7 +57,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 testId = 'grid-icon';
|
||||
const { container, getByTestId } = render(() => (
|
||||
<Grid
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@lucide/shared": ["../shared/src/index.ts"],
|
||||
"@lucide/shared/types": ["../shared/src/build/types.ts"],
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
@@ -1,6 +1,33 @@
|
||||
import { buildLucideIconElement } from '@lucide/shared';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { IconNode, SVGProps } from './types';
|
||||
|
||||
type CreateSVGElementParams = [tag: string, attrs: SVGProps, children?: IconNode];
|
||||
|
||||
/**
|
||||
* Creates a new SVGElement
|
||||
* @param {string} tag - Tag name of the element
|
||||
* @param {object} attrs - Attributes of the element
|
||||
* @param {array} children - Children of the element
|
||||
* @returns {SVGElement}
|
||||
*/
|
||||
const createSVGElement = ([tag, attrs, children]: CreateSVGElementParams) => {
|
||||
const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
||||
|
||||
Object.keys(attrs).forEach((name) => {
|
||||
element.setAttribute(name, String(attrs[name]));
|
||||
});
|
||||
|
||||
if (children?.length) {
|
||||
children.forEach((child) => {
|
||||
const childElement = createSVGElement(child);
|
||||
|
||||
element.appendChild(childElement);
|
||||
});
|
||||
}
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new HTMLElement from icon node
|
||||
* @param {array} iconNode - Icon node to be converted to an element
|
||||
@@ -8,17 +35,13 @@ import { IconNode, SVGProps } from './types';
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
const createElement = (iconNode: IconNode, customAttrs: SVGProps = {}) => {
|
||||
return buildLucideIconElement(
|
||||
document,
|
||||
{
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
includeDefaultClasses: false,
|
||||
attributes: customAttrs,
|
||||
},
|
||||
) as SVGElement;
|
||||
const tag = 'svg';
|
||||
const attrs = {
|
||||
...defaultAttributes,
|
||||
...customAttrs,
|
||||
};
|
||||
|
||||
return createSVGElement([tag, attrs, iconNode]);
|
||||
};
|
||||
|
||||
export default createElement;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { hasA11yProp, mergeClasses, toPascalCase } from '@lucide/shared';
|
||||
import createElement from './createElement';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { Icons, SVGProps } from './types';
|
||||
import { hasA11yProp, mergeClasses, toPascalCase } from '@lucide/shared';
|
||||
|
||||
export type CustomAttrs = { [attr: string]: any };
|
||||
|
||||
@@ -62,15 +63,15 @@ const replaceElement = (element: Element, { nameAttr, icons, attrs }: ReplaceEle
|
||||
}
|
||||
|
||||
const elementAttrs = getAttrs(element);
|
||||
|
||||
const ariaProps = hasA11yProp(elementAttrs) ? {} : { 'aria-hidden': 'true' };
|
||||
const { class: _attrsClassName, ...attrsWithoutClass } = attrs;
|
||||
const { class: _elementClassName, ...elementAttrsWithoutClass } = elementAttrs;
|
||||
|
||||
const iconAttrs = {
|
||||
...defaultAttributes,
|
||||
'data-lucide': iconName,
|
||||
...ariaProps,
|
||||
...attrsWithoutClass,
|
||||
...elementAttrsWithoutClass,
|
||||
...attrs,
|
||||
...elementAttrs,
|
||||
} satisfies SVGProps;
|
||||
|
||||
const elementClassNames = getClassNames(elementAttrs);
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest watch"
|
||||
},
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./types": {
|
||||
"types": "./src/build/types.ts",
|
||||
"default": "./src/build/types.ts"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import buildLucideIconNode from './buildLucideIconNode';
|
||||
import type { LucideBuildParams, LucideIconData, LucideIconNode } from './types';
|
||||
|
||||
/**
|
||||
* Creates a React-compatible Lucide icon node (an svgson-like format) from a Lucide icon object.
|
||||
*
|
||||
* @param icon The icon to build.
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideIconForReact(
|
||||
icon: LucideIconData,
|
||||
params: LucideBuildParams = {},
|
||||
): LucideIconNode {
|
||||
return buildLucideIconNode(icon, {
|
||||
...params,
|
||||
attributeNames: {
|
||||
...params.attributeNames,
|
||||
class: 'className',
|
||||
'stroke-width': 'strokeWidth',
|
||||
'stroke-linecap': 'strokeLinecap',
|
||||
'stroke-linejoin': 'strokeLinejoin',
|
||||
'vector-effect': 'vectorEffect',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default buildLucideIconForReact;
|
||||
@@ -1,32 +1,6 @@
|
||||
import type { LucideBuildParams, LucideIconData, LucideIconNode, SVGProps } from './types';
|
||||
|
||||
export type { LucideBuildParams, LucideIconData, LucideIconNode, SVGProps } from './types';
|
||||
|
||||
const defaultAttributes: SVGProps = {
|
||||
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',
|
||||
};
|
||||
|
||||
const mergeClasses = (...classes: Array<string | undefined>) =>
|
||||
classes
|
||||
.filter(
|
||||
(className, index, array): className is string =>
|
||||
typeof className === 'string' &&
|
||||
className.trim() !== '' &&
|
||||
array.indexOf(className) === index,
|
||||
)
|
||||
.join(' ')
|
||||
.trim();
|
||||
|
||||
const toString = (value: string | number) =>
|
||||
typeof value === 'number' ? value.toString(10) : value.toString();
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { mergeClasses } from '../utils/mergeClasses';
|
||||
|
||||
/**
|
||||
* Creates a Lucide icon node (an svgson-like format) from a Lucide icon object.
|
||||
@@ -35,18 +9,12 @@ const toString = (value: string | number) =>
|
||||
* @param params Additional build parameters.
|
||||
*/
|
||||
function buildLucideIconNode(icon: LucideIconData, params: LucideBuildParams = {}): LucideIconNode {
|
||||
const viewBoxWidth =
|
||||
('size' in icon && icon.size !== undefined
|
||||
? icon.size
|
||||
: 'width' in icon
|
||||
? icon.width
|
||||
: undefined) ?? defaultAttributes['width'];
|
||||
const viewBoxHeight =
|
||||
('size' in icon && icon.size !== undefined
|
||||
? icon.size
|
||||
: 'height' in icon
|
||||
? icon.height
|
||||
: undefined) ?? defaultAttributes['height'];
|
||||
const attributeNames = params.attributeNames ?? {};
|
||||
const getAttributeName = (attributeName: string) =>
|
||||
attributeNames[attributeName] ?? attributeName;
|
||||
|
||||
const viewBoxWidth = icon.size ?? icon.width ?? defaultAttributes['width'];
|
||||
const viewBoxHeight = icon.size ?? icon.height ?? defaultAttributes['height'];
|
||||
const aliasClassNames =
|
||||
icon.aliases
|
||||
?.filter((alias): alias is string => typeof alias === 'string' && alias.trim() !== '')
|
||||
@@ -58,21 +26,29 @@ function buildLucideIconNode(icon: LucideIconData, params: LucideBuildParams = {
|
||||
? mergeClasses(...classNamesFromClassName)
|
||||
: mergeClasses('lucide', ...iconClassNames, ...classNamesFromClassName);
|
||||
|
||||
const calculatedStrokeWidth = params.absoluteStrokeWidth
|
||||
? (Number(params.strokeWidth ?? defaultAttributes['stroke-width']) *
|
||||
Number(icon.size ?? icon.width)) /
|
||||
Number(params.size ?? params.width ?? defaultAttributes['width'])
|
||||
: params.strokeWidth ?? defaultAttributes['stroke-width'];
|
||||
|
||||
const attributes = {
|
||||
...defaultAttributes,
|
||||
...('color' in params && params.color && { stroke: params.color }),
|
||||
...Object.entries(defaultAttributes).reduce((attrs, [attrName, value]) => {
|
||||
attrs[getAttributeName(attrName)] = value;
|
||||
return attrs;
|
||||
}, {} as SVGProps),
|
||||
...('color' in params && params.color && { [getAttributeName('stroke')]: params.color }),
|
||||
...('size' in params &&
|
||||
params.size && {
|
||||
width: toString(params.size),
|
||||
height: toString(params.size),
|
||||
[getAttributeName('width')]: params.size,
|
||||
[getAttributeName('height')]: params.size,
|
||||
}),
|
||||
...('width' in params && params.width && { width: toString(params.width) }),
|
||||
...('height' in params && params.height && { height: toString(params.height) }),
|
||||
...('strokeWidth' in params &&
|
||||
params.strokeWidth && { 'stroke-width': toString(params.strokeWidth) }),
|
||||
...(className && { class: className }),
|
||||
viewBox: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
|
||||
...(params.hasA11yProp === false ? { 'aria-hidden': 'true' } : {}),
|
||||
...('width' in params && params.width && { [getAttributeName('width')]: params.width }),
|
||||
...('height' in params && params.height && { [getAttributeName('height')]: params.height }),
|
||||
[getAttributeName('stroke-width')]: calculatedStrokeWidth,
|
||||
...(className && { [getAttributeName('class')]: className }),
|
||||
[getAttributeName('viewBox')]: `0 0 ${viewBoxWidth} ${viewBoxHeight}`,
|
||||
...(params.hasA11yProp === false ? { [getAttributeName('aria-hidden')]: 'true' } : {}),
|
||||
...('attributes' in params && params.attributes),
|
||||
};
|
||||
|
||||
@@ -81,7 +57,9 @@ function buildLucideIconNode(icon: LucideIconData, params: LucideBuildParams = {
|
||||
attributes,
|
||||
icon.node.map(([name, attrs]) => [
|
||||
name,
|
||||
params.absoluteStrokeWidth ? { 'vector-effect': 'non-scaling-stroke', ...attrs } : attrs,
|
||||
params.nonScalingStroke
|
||||
? { [getAttributeName('vector-effect')]: 'non-scaling-stroke', ...attrs }
|
||||
: attrs,
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,6 +1,4 @@
|
||||
import type { Attrs } from './types.js';
|
||||
|
||||
const defaultAttributes: Attrs = {
|
||||
const defaultAttributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
@@ -10,6 +8,6 @@ const defaultAttributes: Attrs = {
|
||||
'stroke-width': 2,
|
||||
'stroke-linecap': 'round',
|
||||
'stroke-linejoin': 'round',
|
||||
};
|
||||
} as const;
|
||||
|
||||
export default defaultAttributes;
|
||||
@@ -0,0 +1,13 @@
|
||||
const defaultReactAttributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
width: 24,
|
||||
height: 24,
|
||||
viewBox: '0 0 24 24',
|
||||
fill: 'none',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 2,
|
||||
strokeLinecap: 'round',
|
||||
strokeLinejoin: 'round',
|
||||
} as const;
|
||||
|
||||
export default defaultReactAttributes;
|
||||
@@ -1,25 +1,34 @@
|
||||
export type SVGProps = Record<string, any>;
|
||||
export type SVGProps = Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* A Lucide icon node (an svgson-like internal format)
|
||||
*/
|
||||
export type LucideIconNode =
|
||||
| [attrName: string, attributes: SVGProps]
|
||||
| [attrName: string, attributes: SVGProps, children: LucideIconNode[]];
|
||||
export type LucideIconNode<
|
||||
TName extends string = string,
|
||||
TProps extends Record<string, unknown> = SVGProps,
|
||||
> =
|
||||
| [name: TName, attributes: TProps]
|
||||
| [name: TName, attributes: TProps, children: LucideIconNode<TName, TProps>[]];
|
||||
|
||||
/**
|
||||
* A Lucide icon object that fully describes an icon to be displayed.
|
||||
*/
|
||||
export type LucideIconData = {
|
||||
export type LucideIconData<
|
||||
TName extends string = string,
|
||||
TProps extends Record<string, unknown> = SVGProps,
|
||||
> = {
|
||||
name?: string;
|
||||
node: LucideIconNode[];
|
||||
node: LucideIconNode<TName, TProps>[];
|
||||
aliases?: string[];
|
||||
} & ({ size?: number } | { width?: number; height?: number });
|
||||
} & (
|
||||
| { size?: number; width?: never; height?: never }
|
||||
| { size?: never; width?: number; height?: number }
|
||||
);
|
||||
|
||||
/**
|
||||
* Build parameters for creating a Lucide icon instance for display.
|
||||
*/
|
||||
export type LucideBuildParams = {
|
||||
export type LucideBuildParams<TProps extends Record<string, unknown> = SVGProps> = {
|
||||
/**
|
||||
* The color of the icon.
|
||||
*/
|
||||
@@ -29,9 +38,14 @@ export type LucideBuildParams = {
|
||||
*/
|
||||
strokeWidth?: string | number;
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
/**
|
||||
* Adds [`vector-effect="non-scaling-stroke"`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/vector-effect) to child elements.
|
||||
*/
|
||||
nonScalingStroke?: boolean;
|
||||
/**
|
||||
* Extra CSS class names to pass to the SVG element.
|
||||
*/
|
||||
@@ -50,5 +64,12 @@ export type LucideBuildParams = {
|
||||
/**
|
||||
* Any extra attributes to pass to the SVG element.
|
||||
*/
|
||||
attributes?: SVGProps;
|
||||
} & ({ size?: string | number } | { width?: string | number; height?: string | number });
|
||||
attributes?: TProps;
|
||||
/**
|
||||
* The attribute names to use for various HTML attributes.
|
||||
*/
|
||||
attributeNames?: Record<string, string>;
|
||||
} & (
|
||||
| { size?: string | number; width?: never; height?: never }
|
||||
| { size?: never; width?: string | number; height?: string | number }
|
||||
);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
export { default as buildLucideDataUri } from './build/buildLucideDataUri';
|
||||
export { default as buildLucideIconElement } from './build/buildLucideIconElement';
|
||||
export { default as buildLucideIconNode } from './build/buildLucideIconNode';
|
||||
export { default as buildLucideIconForReact } from './build/buildLucideIconForReact.ts';
|
||||
export { default as buildLucideSvg } from './build/buildLucideSvg';
|
||||
export { default as defaultAttributes } from './build/defaultAttributes';
|
||||
export { default as defaultReactAttributes } from './build/defaultReactAttributes.ts';
|
||||
export type { LucideBuildParams, LucideIconData, LucideIconNode, SVGProps } from './build/types';
|
||||
export * from './utils/hasA11yProp';
|
||||
export * from './utils/isEmptyString';
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"scripts": {
|
||||
"build": "pnpm clean && pnpm copy:license && pnpm copy:utils && pnpm build:icons && pnpm build:package && pnpm build:license",
|
||||
"copy:license": "cp ../../LICENSE ./LICENSE",
|
||||
"copy:utils": "mkdir -p ./src/utils && cp -rf ../../packages/shared/src/utils/hasA11yProp.ts ./src/utils/",
|
||||
"copy:utils": "cpy ../../packages/shared/src/build/{buildLucideIconNode.ts,defaultAttributes.ts,types.ts} ../../packages/shared/src/utils/{hasA11yProp.ts,mergeClasses.ts} ./src/utils/",
|
||||
"clean": "rm -rf dist stats ./src/icons/*.{ts,svelte} ./src/aliases/{aliases,prefixed,suffixed}.ts",
|
||||
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mts --exportFileName=index.ts --iconFileExtension=.svelte --importImportFileExtension=.svelte --separateIconFileExport --separateIconFileExportExtension=.ts --withAliases --aliasesFileExtension=.ts --separateAliasesFile --separateAliasesFileExtension=.ts --aliasImportFileExtension=.js --pretty=false",
|
||||
"build:package": "svelte-package --input ./src",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { getHTMLBanner } from './license.mts';
|
||||
import defineExportTemplate from '@lucide/build-icons/utils/defineExportTemplate';
|
||||
|
||||
export default defineExportTemplate(
|
||||
async ({ iconName, iconData, componentName, getSvg, deprecated, deprecationReason }) => {
|
||||
async ({ iconName, iconData, getSvg, deprecated, deprecationReason }) => {
|
||||
const svgContents = await getSvg();
|
||||
const svgBase64 = base64SVG(svgContents);
|
||||
|
||||
@@ -11,9 +11,9 @@ export default defineExportTemplate(
|
||||
<script lang="ts">
|
||||
${getHTMLBanner()}
|
||||
import Icon from '../Icon.svelte';
|
||||
import type { IconProps, LucideIconData } from '../types.js';
|
||||
import type { LucideProps, LucideIconData } from '../types.js';
|
||||
|
||||
let props: IconProps = $props();
|
||||
let props: LucideProps = $props();
|
||||
|
||||
const iconData: LucideIconData = ${JSON.stringify(iconData)};
|
||||
|
||||
@@ -29,7 +29,7 @@ Lucide SVG icon component, renders SVG Element with children.
|
||||
${deprecated ? `\n@deprecated ${deprecationReason}\n` : ''}\
|
||||
-->
|
||||
|
||||
<Icon name={iconData.name} aliases={iconData.aliases} {...props} iconNode={iconData.node} />
|
||||
<Icon icon={iconData} {...props} />
|
||||
`;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,66 +1,56 @@
|
||||
<script lang="ts">
|
||||
import buildLucideIconNode from './utils/buildLucideIconNode.js';
|
||||
import type { IconProps } from './types.js';
|
||||
import type {IconProps} from './types.js';
|
||||
import { hasA11yProp } from './utils/hasA11yProp.js';
|
||||
import { getLucideContext } from './context.js';
|
||||
import {mergeClasses} from './utils/mergeClasses.js';
|
||||
import type {ClassValue} from "svelte/elements";
|
||||
|
||||
const globalProps = getLucideContext() ?? {};
|
||||
|
||||
const {
|
||||
name,
|
||||
aliases,
|
||||
color = globalProps.color ?? 'currentColor',
|
||||
size = globalProps.size ?? 24,
|
||||
width = size,
|
||||
height = size,
|
||||
strokeWidth = globalProps.strokeWidth ?? 2,
|
||||
absoluteStrokeWidth = globalProps.absoluteStrokeWidth ?? false,
|
||||
nonScalingStroke = globalProps.nonScalingStroke ?? false,
|
||||
iconNode = [],
|
||||
icon = {
|
||||
node: iconNode,
|
||||
aliases: [],
|
||||
size: 24,
|
||||
},
|
||||
class: propsClass,
|
||||
children,
|
||||
...props
|
||||
}: IconProps = $props();
|
||||
|
||||
const calculatedStrokeWidth = $derived(
|
||||
absoluteStrokeWidth ? (Number(strokeWidth) * 24) / Number(size) : strokeWidth,
|
||||
);
|
||||
|
||||
const iconClassName = $derived(
|
||||
['lucide-icon', globalProps.class, props.class]
|
||||
.filter(
|
||||
(className): className is string =>
|
||||
typeof className === 'string' && className.trim() !== '',
|
||||
)
|
||||
.join(' '),
|
||||
);
|
||||
|
||||
const hasAccessibleProp = $derived(
|
||||
Boolean(children) || hasA11yProp(props as Record<string, any>),
|
||||
);
|
||||
|
||||
const [, builtSvgAttributes, builtIconNode = []] = $derived(
|
||||
const [, svgAttributes, builtIconNode] = $derived(
|
||||
buildLucideIconNode(
|
||||
icon,
|
||||
{
|
||||
name,
|
||||
aliases,
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
size,
|
||||
color,
|
||||
strokeWidth: calculatedStrokeWidth,
|
||||
className: iconClassName,
|
||||
width,
|
||||
height,
|
||||
strokeWidth,
|
||||
absoluteStrokeWidth,
|
||||
nonScalingStroke,
|
||||
// @TODO: maybe drop the extra `lucide-icon` class altogether.
|
||||
className: mergeClasses<ClassValue | null | undefined>('lucide-icon', propsClass),
|
||||
hasA11yProp: hasAccessibleProp,
|
||||
attributes: props,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const iconAttributes = $derived({
|
||||
...builtSvgAttributes,
|
||||
...props,
|
||||
width: builtSvgAttributes.width,
|
||||
height: builtSvgAttributes.height,
|
||||
stroke: builtSvgAttributes.stroke,
|
||||
'stroke-width': builtSvgAttributes['stroke-width'],
|
||||
class: builtSvgAttributes.class,
|
||||
...svgAttributes,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@ export interface LucideGlobalContext {
|
||||
color?: string;
|
||||
size?: number;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export * from './icons/index.js';
|
||||
export * as icons from './icons/index.js';
|
||||
export * from './aliases/index.js';
|
||||
export { default as defaultAttributes } from './defaultAttributes.js';
|
||||
export { default as defaultAttributes } from './utils/defaultAttributes.js';
|
||||
export * from './types.js';
|
||||
export { default as Icon } from './Icon.svelte';
|
||||
export * from './context.js';
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { SVGAttributes } from 'svelte/elements';
|
||||
import type { Snippet, Component } from 'svelte';
|
||||
import type {
|
||||
LucideIconData as SharedLucideIconData,
|
||||
LucideIconNode as SharedLucideIconNode,
|
||||
} from '@lucide/shared/types';
|
||||
|
||||
export type Attrs = SVGAttributes<SVGSVGElement>;
|
||||
type IconNodeElements =
|
||||
@@ -12,27 +16,39 @@ type IconNodeElements =
|
||||
| 'polyline'
|
||||
| 'rect';
|
||||
|
||||
export type IconNode = [elementName: IconNodeElements, attrs: Attrs][];
|
||||
export type LucideIconNode = SharedLucideIconNode<IconNodeElements, Attrs>;
|
||||
|
||||
export interface LucideIconData {
|
||||
name: string;
|
||||
node: IconNode;
|
||||
aliases?: string[];
|
||||
}
|
||||
export type LucideIconData = SharedLucideIconData<IconNodeElements, Attrs>;
|
||||
|
||||
export interface LucideProps extends Attrs {
|
||||
name?: string;
|
||||
aliases?: string[];
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export type LucideProps = Attrs & {
|
||||
color?: string;
|
||||
size?: number | string;
|
||||
strokeWidth?: number | string;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
iconNode?: IconNode;
|
||||
nonScalingStroke?: boolean;
|
||||
children?: Snippet;
|
||||
title?: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type IconProps = LucideProps;
|
||||
export type IconProps = LucideProps &
|
||||
(
|
||||
| {
|
||||
icon: LucideIconData;
|
||||
iconNode?: never;
|
||||
}
|
||||
| {
|
||||
icon?: never;
|
||||
iconNode: LucideIconNode[];
|
||||
}
|
||||
);
|
||||
|
||||
export type LucideIcon = Component<LucideProps>;
|
||||
|
||||
|
||||
@@ -114,41 +114,6 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should not scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-smile lucide-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
>
|
||||
</circle>
|
||||
<path d="M8 14s1.5 2 4 2 4-2 4-2">
|
||||
</path>
|
||||
<line x1="9"
|
||||
x2="9.01"
|
||||
y1="9"
|
||||
y2="9"
|
||||
>
|
||||
</line>
|
||||
<line x1="15"
|
||||
x2="15.01"
|
||||
y1="9"
|
||||
y2="9"
|
||||
>
|
||||
</line>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should render an component 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
@@ -268,3 +233,38 @@ exports[`Using lucide icon components > should render an icon slot 1`] = `
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Using lucide icon components > should scale the strokeWidth when absoluteStrokeWidth is set 1`] = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
viewbox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="red"
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-smile lucide-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
>
|
||||
</circle>
|
||||
<path d="M8 14s1.5 2 4 2 4-2 4-2">
|
||||
</path>
|
||||
<line x1="9"
|
||||
x2="9.01"
|
||||
y1="9"
|
||||
y2="9"
|
||||
>
|
||||
</line>
|
||||
<line x1="15"
|
||||
x2="15.01"
|
||||
y1="9"
|
||||
y2="9"
|
||||
>
|
||||
</line>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
|
||||
+61
-58
@@ -1,26 +1,48 @@
|
||||
import { computed, type FunctionalComponent, h } from 'vue';
|
||||
import { buildLucideIconNode, isEmptyString, toKebabCase } from '@lucide/shared';
|
||||
import { type FunctionalComponent, h } from 'vue';
|
||||
import {
|
||||
buildLucideIconNode,
|
||||
hasA11yProp,
|
||||
isEmptyString,
|
||||
mergeClasses,
|
||||
toKebabCase,
|
||||
} from '@lucide/shared';
|
||||
import defaultAttributes from './defaultAttributes';
|
||||
import { IconNode, LucideProps } from './types';
|
||||
import { LucideIconNode, LucideIconData, LucideProps } from './types';
|
||||
import { useLucideProps } from './context';
|
||||
|
||||
interface IconProps {
|
||||
iconNode: IconNode;
|
||||
name: string;
|
||||
aliases?: string[];
|
||||
}
|
||||
type IconProps =
|
||||
| {
|
||||
icon: LucideIconData;
|
||||
iconNode?: never;
|
||||
name?: never;
|
||||
}
|
||||
| {
|
||||
icon?: never;
|
||||
iconNode: LucideIconNode[];
|
||||
name: string;
|
||||
};
|
||||
|
||||
const Icon: FunctionalComponent<LucideProps & IconProps> = (
|
||||
{
|
||||
name,
|
||||
aliases,
|
||||
iconNode,
|
||||
iconNode = [],
|
||||
icon = {
|
||||
name: toKebabCase(name),
|
||||
node: iconNode,
|
||||
size: 24,
|
||||
aliases: [],
|
||||
},
|
||||
absoluteStrokeWidth,
|
||||
'absolute-stroke-width': absoluteStrokeWidthKebabCase,
|
||||
nonScalingStroke,
|
||||
'non-scaling-stroke': nonScalingStrokeKebabCase,
|
||||
strokeWidth,
|
||||
'stroke-width': strokeWidthKebabCase,
|
||||
size,
|
||||
width = size,
|
||||
height = size,
|
||||
color,
|
||||
class: classes = '',
|
||||
...props
|
||||
},
|
||||
{ slots },
|
||||
@@ -30,62 +52,43 @@ const Icon: FunctionalComponent<LucideProps & IconProps> = (
|
||||
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)
|
||||
);
|
||||
}
|
||||
const { class: propsClass, ...restProps } = props;
|
||||
|
||||
return strokeWidthValue;
|
||||
const [, svgAttributes, builtIconNode] = buildLucideIconNode(icon, {
|
||||
color: color ?? contextColor,
|
||||
width: width ?? size ?? contextSize,
|
||||
height: height ?? size ?? contextSize,
|
||||
strokeWidth:
|
||||
strokeWidth ??
|
||||
strokeWidthKebabCase ??
|
||||
contextStrokeWidth ??
|
||||
defaultAttributes['stroke-width'],
|
||||
absoluteStrokeWidth: isAbsoluteStrokeWidth,
|
||||
nonScalingStroke: isNonScalingStroke,
|
||||
className: mergeClasses(contextClass, propsClass),
|
||||
hasA11yProp: !!slots || hasA11yProp(restProps),
|
||||
attributes: restProps,
|
||||
});
|
||||
|
||||
const normalizedName = computed(() => (name != null ? toKebabCase(name) : undefined));
|
||||
|
||||
const iconName = computed(() => (normalizedName.value ? `${normalizedName.value}-icon` : 'icon'));
|
||||
|
||||
const [, svgAttributes, builtIconNode = []] = buildLucideIconNode(
|
||||
{
|
||||
name: iconName.value,
|
||||
aliases,
|
||||
size: 24,
|
||||
node: iconNode,
|
||||
},
|
||||
{
|
||||
size: size ?? contextSize ?? defaultAttributes.width,
|
||||
color: color ?? contextColor ?? defaultAttributes.stroke,
|
||||
strokeWidth: calculatedStrokeWidth.value,
|
||||
className: contextClass,
|
||||
},
|
||||
);
|
||||
|
||||
const iconAttributes = {
|
||||
...svgAttributes,
|
||||
...props,
|
||||
width: svgAttributes.width,
|
||||
height: svgAttributes.height,
|
||||
stroke: svgAttributes.stroke,
|
||||
'stroke-width': svgAttributes['stroke-width'],
|
||||
class: svgAttributes.class,
|
||||
};
|
||||
|
||||
return h('svg', iconAttributes, [
|
||||
return h('svg', svgAttributes, [
|
||||
...builtIconNode.map((child) => h(...child)),
|
||||
...(slots.default ? [slots.default()] : []),
|
||||
]);
|
||||
|
||||
@@ -6,7 +6,11 @@ interface LucideIconsContext {
|
||||
size?: number;
|
||||
color?: string;
|
||||
strokeWidth?: number;
|
||||
/**
|
||||
* @deprecated Use `nonScalingStroke` instead.
|
||||
*/
|
||||
absoluteStrokeWidth?: boolean;
|
||||
nonScalingStroke?: boolean;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,44 @@
|
||||
import { h } from 'vue';
|
||||
import { LucideIcon, LucideIconData } from './types';
|
||||
import { LucideIcon, LucideIconData, LucideIconNode } from './types';
|
||||
import Icon from './Icon';
|
||||
|
||||
/**
|
||||
* Create a Lucide icon component
|
||||
* @param {object} iconData
|
||||
* @returns {FunctionalComponent} LucideIcon
|
||||
* Creates a Lucide icon component from icon data.
|
||||
*
|
||||
* @param icon Icon data object.
|
||||
* @returns Lucide icon component.
|
||||
*/
|
||||
const createLucideIcon =
|
||||
(iconData: LucideIconData): 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: iconData.node,
|
||||
name: iconData.name,
|
||||
aliases: iconData.aliases,
|
||||
icon,
|
||||
},
|
||||
slots.default ? { default: slots.default } : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
export default createLucideIcon;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,16 +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<SVGAttributes> {
|
||||
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<string, string | number>][];
|
||||
export interface LucideIconData {
|
||||
name: string;
|
||||
node: IconNode;
|
||||
aliases?: string[];
|
||||
}
|
||||
export type LucideIconNode = SharedLucideIconNode;
|
||||
|
||||
export type LucideIconData = SharedLucideIconData;
|
||||
|
||||
/**
|
||||
* @deprecated Use LucideIconNode instead.
|
||||
*/
|
||||
export type IconNode = LucideIconNode[];
|
||||
|
||||
export type LucideIcon = FunctionalComponent<LucideProps>;
|
||||
|
||||
@@ -5,21 +5,9 @@ import { airVent } from './testIconNodes';
|
||||
import { Icon } from '../src/lucide-vue';
|
||||
|
||||
describe('Using Icon Component', () => {
|
||||
it('should render icon based on a iconNode', async () => {
|
||||
const { container } = render(Icon, {
|
||||
props: {
|
||||
iconNode: airVent,
|
||||
name: 'AirVent',
|
||||
size: 48,
|
||||
color: 'red',
|
||||
absoluteStrokeWidth: true,
|
||||
},
|
||||
});
|
||||
const airVentIcon = { name: 'air-vent', node: airVent, size: 24 };
|
||||
|
||||
expect(container.firstChild).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render icon and match snapshot', async () => {
|
||||
it('should render icon based on an icon node', async () => {
|
||||
const { container } = render(Icon, {
|
||||
props: {
|
||||
iconNode: airVent,
|
||||
@@ -32,4 +20,42 @@ describe('Using Icon Component', () => {
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render icon based on icon data', async () => {
|
||||
const { container } = render(Icon, {
|
||||
props: {
|
||||
icon: airVentIcon,
|
||||
size: 48,
|
||||
color: 'red',
|
||||
absoluteStrokeWidth: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render icon and match snapshot', async () => {
|
||||
const { container } = render(Icon, {
|
||||
props: {
|
||||
icon: airVentIcon,
|
||||
size: 48,
|
||||
color: 'red',
|
||||
absoluteStrokeWidth: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should render icon based on icon data', async () => {
|
||||
const { container } = render(Icon, {
|
||||
props: {
|
||||
icon: airVentIcon,
|
||||
size: 48,
|
||||
color: 'red',
|
||||
},
|
||||
});
|
||||
|
||||
expect(container.firstChild).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,63 @@
|
||||
|
||||
exports[`Using Icon Component > should render icon and match snapshot 1`] = `
|
||||
<svg
|
||||
class="lucide lucide-air-vent-icon"
|
||||
class="lucide lucide-air-vent"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||
/>
|
||||
<path
|
||||
d="M6 8h12"
|
||||
/>
|
||||
<path
|
||||
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
|
||||
/>
|
||||
<path
|
||||
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using Icon Component > should render icon based on an icon node 1`] = `
|
||||
<svg
|
||||
class="lucide lucide-air-vent"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="1"
|
||||
viewBox="0 0 24 24"
|
||||
width="48"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 12H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"
|
||||
/>
|
||||
<path
|
||||
d="M6 8h12"
|
||||
/>
|
||||
<path
|
||||
d="M18.3 17.7a2.5 2.5 0 0 1-3.16 3.83 2.53 2.53 0 0 1-1.14-2V12"
|
||||
/>
|
||||
<path
|
||||
d="M6.6 15.6A2 2 0 1 0 10 17v-5"
|
||||
/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
exports[`Using Icon Component > should render icon based on icon data 1`] = `
|
||||
<svg
|
||||
class="lucide lucide-air-vent"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`Using lucide icon context > should render the icon with LucideProvider 1`] = `
|
||||
<svg
|
||||
class="lucide lucide-house-icon lucide-home provider-class"
|
||||
class="lucide lucide-house lucide-home provider-class"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`Using lucide icon components > should add a class to the element 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="lucide lucide-smile-icon my-icon"
|
||||
class="lucide lucide-smile my-icon"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
@@ -41,7 +41,7 @@ exports[`Using lucide icon components > should add a class to the element 1`] =
|
||||
exports[`Using lucide icon components > should add a style attribute to the element 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="lucide lucide-smile-icon"
|
||||
class="lucide lucide-smile"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
@@ -80,7 +80,7 @@ exports[`Using lucide icon components > should add a style attribute to the elem
|
||||
exports[`Using lucide icon components > should adjust the size, stroke color and stroke width 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="lucide lucide-smile-icon"
|
||||
class="lucide lucide-smile"
|
||||
fill="none"
|
||||
height="48"
|
||||
stroke="red"
|
||||
@@ -118,7 +118,7 @@ exports[`Using lucide icon components > should adjust the size, stroke color and
|
||||
exports[`Using lucide icon components > should pass children to the icon slot 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="lucide lucide-smile-icon"
|
||||
class="lucide lucide-smile"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
@@ -161,7 +161,7 @@ exports[`Using lucide icon components > should pass children to the icon slot 1`
|
||||
exports[`Using lucide icon components > should render an component 1`] = `
|
||||
<div>
|
||||
<svg
|
||||
class="lucide lucide-smile-icon"
|
||||
class="lucide lucide-smile"
|
||||
fill="none"
|
||||
height="24"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Using lucide icon context', () => {
|
||||
|
||||
expect(IconComponent).toHaveAttribute(
|
||||
'class',
|
||||
'lucide lucide-house-icon lucide-home provider-class icon-class',
|
||||
'lucide lucide-house lucide-home provider-class icon-class',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user