@uniweb/kit 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/components/Icon/Icon.jsx +45 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"fuse.js": "^7.0.0",
|
|
40
40
|
"shiki": "^3.0.0",
|
|
41
41
|
"tailwind-merge": "^2.6.0",
|
|
42
|
-
"@uniweb/core": "0.3.
|
|
42
|
+
"@uniweb/core": "0.3.3"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -38,32 +38,42 @@ const BUILT_IN_ICONS = {
|
|
|
38
38
|
* @param {string} svgContent - Raw SVG string
|
|
39
39
|
* @returns {Object} { viewBox, content, width, height }
|
|
40
40
|
*/
|
|
41
|
+
/**
|
|
42
|
+
* Extract an attribute value from an SVG opening tag string
|
|
43
|
+
*/
|
|
44
|
+
function getAttr(svgTag, name) {
|
|
45
|
+
const match = svgTag.match(new RegExp(`${name}="([^"]*)"`, 'i'))
|
|
46
|
+
return match ? match[1] : null
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
function parseSvg(svgContent) {
|
|
42
50
|
if (!svgContent) return null
|
|
43
51
|
|
|
44
52
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
53
|
+
// Extract the <svg ...> opening tag and inner content
|
|
54
|
+
// Works in both browser and Node.js (no DOMParser needed)
|
|
55
|
+
const svgTagMatch = svgContent.match(/<svg\s[^>]*>/i)
|
|
56
|
+
if (!svgTagMatch) return null
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
const svgTag = svgTagMatch[0]
|
|
50
59
|
|
|
51
|
-
const viewBox =
|
|
52
|
-
const width =
|
|
53
|
-
const height =
|
|
60
|
+
const viewBox = getAttr(svgTag, 'viewBox') || '0 0 24 24'
|
|
61
|
+
const width = getAttr(svgTag, 'width')
|
|
62
|
+
const height = getAttr(svgTag, 'height')
|
|
54
63
|
|
|
55
64
|
// Preserve SVG presentation attributes from the source
|
|
56
65
|
// Different icon families use different rendering styles:
|
|
57
66
|
// - Lucide, Feather, Heroicons: stroke-based (fill="none", stroke="currentColor")
|
|
58
67
|
// - Font Awesome, Bootstrap: fill-based (fill="currentColor")
|
|
59
|
-
const fill =
|
|
60
|
-
const stroke =
|
|
61
|
-
const strokeWidth =
|
|
62
|
-
const strokeLinecap =
|
|
63
|
-
const strokeLinejoin =
|
|
68
|
+
const fill = getAttr(svgTag, 'fill')
|
|
69
|
+
const stroke = getAttr(svgTag, 'stroke')
|
|
70
|
+
const strokeWidth = getAttr(svgTag, 'stroke-width')
|
|
71
|
+
const strokeLinecap = getAttr(svgTag, 'stroke-linecap')
|
|
72
|
+
const strokeLinejoin = getAttr(svgTag, 'stroke-linejoin')
|
|
64
73
|
|
|
65
|
-
// Get inner content
|
|
66
|
-
const
|
|
74
|
+
// Get inner content (everything between <svg> and </svg>)
|
|
75
|
+
const innerMatch = svgContent.match(/<svg\s[^>]*>([\s\S]*)<\/svg>/i)
|
|
76
|
+
const content = innerMatch ? innerMatch[1] : ''
|
|
67
77
|
|
|
68
78
|
return {
|
|
69
79
|
viewBox, content, width, height,
|
|
@@ -128,25 +138,37 @@ export function Icon({
|
|
|
128
138
|
errorComponent,
|
|
129
139
|
...props
|
|
130
140
|
}) {
|
|
131
|
-
const [fetchedSvg, setFetchedSvg] = useState(null)
|
|
132
|
-
const [loading, setLoading] = useState(false)
|
|
133
|
-
const [error, setError] = useState(false)
|
|
134
|
-
|
|
135
141
|
// Normalize props (handle legacy icon object)
|
|
136
142
|
const iconLibrary = library || (typeof icon === 'object' ? icon.library : null)
|
|
137
143
|
const iconUrl = url || (typeof icon === 'string' ? icon : icon?.url)
|
|
138
144
|
const iconSvg = svg || (typeof icon === 'object' ? icon.svg : null)
|
|
139
145
|
const iconName = name || (typeof icon === 'object' ? icon.name : null)
|
|
140
146
|
|
|
141
|
-
//
|
|
147
|
+
// Check sync cache for SSR (pre-populated by prerender)
|
|
148
|
+
const cachedSvg = useMemo(() => {
|
|
149
|
+
if (iconLibrary && iconName) {
|
|
150
|
+
const uniweb = getUniweb()
|
|
151
|
+
return uniweb?.getIconSync?.(iconLibrary, iconName) || null
|
|
152
|
+
}
|
|
153
|
+
return null
|
|
154
|
+
}, [iconLibrary, iconName])
|
|
155
|
+
|
|
156
|
+
const [fetchedSvg, setFetchedSvg] = useState(cachedSvg)
|
|
157
|
+
const [loading, setLoading] = useState(false)
|
|
158
|
+
const [error, setError] = useState(false)
|
|
159
|
+
|
|
160
|
+
// Fetch SVG from URL or resolve from library (client-side only)
|
|
142
161
|
useEffect(() => {
|
|
162
|
+
// Already have SVG from cache or direct prop
|
|
163
|
+
if (cachedSvg || iconSvg) {
|
|
164
|
+
setFetchedSvg(cachedSvg)
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
143
168
|
// Reset state when source changes
|
|
144
169
|
setFetchedSvg(null)
|
|
145
170
|
setError(false)
|
|
146
171
|
|
|
147
|
-
// Direct SVG - no fetch needed
|
|
148
|
-
if (iconSvg) return
|
|
149
|
-
|
|
150
172
|
// URL-based fetch
|
|
151
173
|
if (iconUrl) {
|
|
152
174
|
setLoading(true)
|
|
@@ -191,7 +213,7 @@ export function Icon({
|
|
|
191
213
|
})
|
|
192
214
|
}
|
|
193
215
|
}
|
|
194
|
-
}, [iconUrl, iconSvg, iconLibrary, iconName])
|
|
216
|
+
}, [iconUrl, iconSvg, iconLibrary, iconName, cachedSvg])
|
|
195
217
|
|
|
196
218
|
// Determine the SVG content to render
|
|
197
219
|
const svgData = useMemo(() => {
|