createBrivenIcon.ts106 lines · main
1import {
2 createElement,
3 forwardRef,
4 ForwardRefExoticComponent,
5 RefAttributes,
6 SVGElementType,
7 SVGProps,
8} from 'react'
9
10import defaultAttributes from './defaultAttributes'
11
12export type IconNode = [elementName: SVGElementType, attrs: Record<string, string>][]
13
14export type SVGAttributes = Partial<SVGProps<SVGSVGElement>>
15type ComponentAttributes = RefAttributes<SVGSVGElement> & SVGAttributes
16
17export interface LucideProps extends ComponentAttributes {
18 size?: string | number
19 absoluteStrokeWidth?: boolean
20}
21
22export type LucideIcon = ForwardRefExoticComponent<LucideProps>
23/**
24 * Converts string to KebabCase
25 * Copied from scripts/helper. If anyone knows how to properly import it here
26 * then please fix it.
27 *
28 * @param {string} string
29 * @returns {string} A kebabized string
30 */
31export const toKebabCase = (string: string) =>
32 string
33 .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
34 .toLowerCase()
35 .trim()
36
37/**
38 * Converts kebab-case string to camelCase
39 * @param {string} string
40 * @returns {string} A camelCased string
41 */
42export const toCamelCase = (string: string) =>
43 string.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()).trim()
44
45/**
46 * Converts kebab-case attributes to camelCase for React compatibility
47 * @param {Record<string, string>} attrs
48 * @returns {Record<string, string>} Attributes with camelCase keys
49 */
50export const convertAttributesToCamelCase = (
51 attrs: Record<string, string>
52): Record<string, string> => {
53 const converted: Record<string, string> = {}
54
55 for (const [key, value] of Object.entries(attrs)) {
56 // Convert kebab-case to camelCase, but keep some special cases
57 const camelKey = key.includes('-') ? toCamelCase(key) : key
58 converted[camelKey] = value
59 }
60
61 return converted
62}
63
64const createLucideIcon = (
65 iconName: string,
66 iconNode: IconNode,
67 svgDefaults?: Record<string, string>
68): LucideIcon => {
69 const Component = forwardRef<SVGSVGElement, LucideProps>(
70 (
71 { color, size = 24, strokeWidth, absoluteStrokeWidth, className = '', children, ...rest },
72 ref
73 ) => {
74 return createElement(
75 'svg',
76 {
77 ref,
78 ...defaultAttributes,
79 ...svgDefaults,
80 width: size,
81 height: size,
82 ...(color !== undefined && { stroke: color }),
83 ...(strokeWidth !== undefined && {
84 strokeWidth: absoluteStrokeWidth
85 ? (Number(strokeWidth) * 24) / Number(size)
86 : strokeWidth,
87 }),
88 className: ['lucide', `lucide-${toKebabCase(iconName)}`, className].join(' '),
89 ...rest,
90 },
91 [
92 ...iconNode.map(([tag, attrs]) =>
93 createElement(tag, convertAttributesToCamelCase(attrs))
94 ),
95 ...(Array.isArray(children) ? children : [children]),
96 ]
97 )
98 }
99 )
100
101 Component.displayName = `${iconName}`
102
103 return Component
104}
105
106export default createLucideIcon