@vc-shell/framework 1.1.58 → 1.1.59

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/assets/154365acf3010d96.woff2 +0 -0
  3. package/dist/assets/475937116ee3314a.woff2 +0 -0
  4. package/dist/assets/49791943b3872376.woff2 +0 -0
  5. package/dist/assets/7a5aa5abd625137f.ttf +0 -0
  6. package/dist/assets/ac1a99b3d05d8232.woff +0 -0
  7. package/dist/assets/ccc878931d74181b.woff2 +0 -0
  8. package/dist/assets/ee6983981ffcbb41.woff2 +0 -0
  9. package/dist/framework.js +3670 -3644
  10. package/dist/index.css +9 -1
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/ui/components/atoms/vc-icon/vc-bootstrap-icon.vue.d.ts.map +1 -1
  15. package/dist/ui/components/atoms/vc-icon/vc-fontawesome-icon.vue.d.ts.map +1 -1
  16. package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts.map +1 -1
  17. package/dist/ui/components/atoms/vc-icon/vc-lucide-icon.vue.d.ts +5 -0
  18. package/dist/ui/components/atoms/vc-icon/vc-lucide-icon.vue.d.ts.map +1 -1
  19. package/dist/ui/components/atoms/vc-icon/vc-material-icon.vue.d.ts +6 -1
  20. package/dist/ui/components/atoms/vc-icon/vc-material-icon.vue.d.ts.map +1 -1
  21. package/dist/{vendor-intlify-core-base-Bvt2vJFV.js → vendor-intlify-core-base-DZ8xhqFL.js} +4 -4
  22. package/dist/{vendor-intlify-message-compiler-1VxNzq21.js → vendor-intlify-message-compiler-8PCyu80g.js} +2 -2
  23. package/dist/{vendor-intlify-shared-D7kiPMOG.js → vendor-intlify-shared-BBJw7VuB.js} +1 -1
  24. package/dist/vendor-lucide-vue-next-m0L4DzUL.js +29780 -0
  25. package/dist/{vendor-vue-i18n-DPtOLen3.js → vendor-vue-i18n-CXIkMpzB.js} +5 -5
  26. package/package.json +8 -5
  27. package/ui/components/atoms/vc-icon/composables/use-icon.ts +1 -1
  28. package/ui/components/atoms/vc-icon/vc-bootstrap-icon.vue +5 -6
  29. package/ui/components/atoms/vc-icon/vc-fontawesome-icon.vue +31 -37
  30. package/ui/components/atoms/vc-icon/vc-icon.vue +186 -43
  31. package/ui/components/atoms/vc-icon/vc-lucide-icon.vue +70 -23
  32. package/ui/components/atoms/vc-icon/vc-material-icon.vue +37 -45
  33. package/dist/vendor-iconify-vue-D4fihzvl.js +0 -1205
@@ -1,14 +1,14 @@
1
- import { C as Yt, D as qe, u as B, c as jt, a as Ce, b as De, i as K, A as Bt, s as Xt, g as Jt, N as Kt, d as zt, e as qt, f as Qe, h as Qt, j as Se, p as ye, t as Ve, M as Me, k as Ue, l as We, m as $e, n as we, o as Zt, q as ea, r as ta, v as aa, w as na, x as sa, y as la, z as ra, B as oa } from "./vendor-intlify-core-base-Bvt2vJFV.js";
2
- import { w as Ze, e as N, l as ia, A as S, a as F, B as Ne, b, i as P, f as le, r as re, d as C, c as A, C as se, k as oe, j as R, g as $, n as z, h as Te, o as ca, m as J } from "./vendor-intlify-shared-D7kiPMOG.js";
1
+ import { C as Yt, D as qe, u as B, c as jt, a as Ce, b as De, i as K, A as Bt, s as Xt, g as Jt, N as Kt, d as zt, e as qt, f as Qe, h as Qt, j as Se, p as ye, t as Ve, M as Me, k as Ue, l as We, m as $e, n as we, o as Zt, q as ea, r as ta, v as aa, w as na, x as sa, y as la, z as ra, B as oa } from "./vendor-intlify-core-base-DZ8xhqFL.js";
2
+ import { w as Ze, e as N, l as ia, A as S, a as F, B as Ne, b, i as P, f as le, r as re, d as C, c as A, C as se, k as oe, j as R, g as $, n as z, h as Te, o as ca, m as J } from "./vendor-intlify-shared-BBJw7VuB.js";
3
3
  import { effectScope as _a, ref as ua, shallowRef as ma, computed as X, watch as ge, isRef as fa, defineComponent as Le, getCurrentInstance as q, h as et, Fragment as tt, inject as ga, onMounted as Ea, onUnmounted as da, createVNode as ba, Text as va } from "vue";
4
4
  import { s as Ia } from "./vendor-vue-devtools-api-aVYGocXp.js";
5
- import { c as pa } from "./vendor-intlify-message-compiler-1VxNzq21.js";
5
+ import { c as pa } from "./vendor-intlify-message-compiler-8PCyu80g.js";
6
6
  /*!
7
- * vue-i18n v11.1.7
7
+ * vue-i18n v11.1.9
8
8
  * (c) 2025 kazuya kawaguchi
9
9
  * Released under the MIT License.
10
10
  */
11
- const at = "11.1.7";
11
+ const at = "11.1.9";
12
12
  function Na() {
13
13
  typeof __VUE_I18N_FULL_INSTALL__ != "boolean" && (J().__VUE_I18N_FULL_INSTALL__ = !0), typeof __VUE_I18N_LEGACY_API__ != "boolean" && (J().__VUE_I18N_LEGACY_API__ = !0), typeof __INTLIFY_DROP_MESSAGE_COMPILER__ != "boolean" && (J().__INTLIFY_DROP_MESSAGE_COMPILER__ = !1), typeof __INTLIFY_PROD_DEVTOOLS__ != "boolean" && (J().__INTLIFY_PROD_DEVTOOLS__ = !1);
14
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vc-shell/framework",
3
- "version": "1.1.58",
3
+ "version": "1.1.59",
4
4
  "type": "module",
5
5
  "main": "./dist/framework.js",
6
6
  "types": "./dist/index.d.ts",
@@ -41,8 +41,9 @@
41
41
  "dependencies": {
42
42
  "@floating-ui/vue": "^1.0.6",
43
43
  "@fontsource/plus-jakarta-sans": "^5.1.0",
44
+ "@fortawesome/fontawesome-free": "^6.7.2",
44
45
  "@headlessui/vue": "^1.7.19",
45
- "@iconify/vue": "^5.0.0",
46
+ "@material-symbols/font-300": "^0.32.0",
46
47
  "@microsoft/signalr": "^8.0.0",
47
48
  "@tiptap/extension-image": "^2.25.0",
48
49
  "@tiptap/extension-link": "^2.25.0",
@@ -61,9 +62,11 @@
61
62
  "@vueuse/components": "^10.7.1",
62
63
  "@vueuse/core": "^10.7.1",
63
64
  "@vueuse/integrations": "^10.7.1",
65
+ "bootstrap-icons": "^1.11.3",
64
66
  "core-js": "^3.35.0",
65
67
  "dompurify": "^3.0.11",
66
68
  "iso-639-1": "^3.1.0",
69
+ "lucide-vue-next": "^0.503.0",
67
70
  "moment": "^2.30.1",
68
71
  "normalize.css": "^8.0.1",
69
72
  "swiper": "^6.8.4",
@@ -82,9 +85,9 @@
82
85
  "@fullhuman/postcss-purgecss": "^7.0.2",
83
86
  "@laynezh/vite-plugin-lib-assets": "v1.1.0",
84
87
  "@types/dompurify": "^3.0.5",
85
- "@vc-shell/api-client-generator": "^1.1.58",
86
- "@vc-shell/config-generator": "^1.1.58",
87
- "@vc-shell/ts-config": "^1.1.58",
88
+ "@vc-shell/api-client-generator": "^1.1.59",
89
+ "@vc-shell/config-generator": "^1.1.59",
90
+ "@vc-shell/ts-config": "^1.1.59",
88
91
  "@vitejs/plugin-vue": "^5.2.3",
89
92
  "@vue/test-utils": "^2.4.5",
90
93
  "cypress-signalr-mock": "^1.5.0",
@@ -90,7 +90,7 @@ export function useIcon(options: UseIconOptions) {
90
90
  const styles: Record<string, string> = {};
91
91
 
92
92
  // For SVG and Lucide icons, we need width and height
93
- if (type === "svg") {
93
+ if (type === "svg" || type === "lucide") {
94
94
  styles.width = "1em";
95
95
  styles.height = "1em";
96
96
  }
@@ -1,19 +1,18 @@
1
1
  <template>
2
- <Icon
3
- :icon="iconName"
2
+ <i
4
3
  :class="[
5
4
  'vc-bootstrap-icon',
6
5
  !hasCustomSize && `vc-bootstrap-icon--${size}`,
6
+ iconClass,
7
7
  variant ? `vc-bootstrap-icon--${variant}` : '',
8
8
  ]"
9
9
  :style="mergedStyle"
10
10
  aria-hidden="true"
11
- />
11
+ ></i>
12
12
  </template>
13
13
 
14
14
  <script lang="ts" setup>
15
15
  import { computed } from "vue";
16
- import { Icon } from "@iconify/vue";
17
16
  import type { IconSize, IconVariant } from "./types";
18
17
  import { useIcon } from "./composables";
19
18
 
@@ -67,11 +66,11 @@ const mergedStyle = computed(() => {
67
66
  });
68
67
 
69
68
  // Compute proper Bootstrap icon class
70
- const iconName = computed(() => {
69
+ const iconClass = computed(() => {
71
70
  if (!props.icon) return "";
72
71
 
73
72
  const icon = props.icon.startsWith("bi-") ? props.icon.substring(3) : props.icon;
74
- return `bi:${icon}`;
73
+ return `bi-${icon}`;
75
74
  });
76
75
  </script>
77
76
 
@@ -1,15 +1,18 @@
1
1
  <template>
2
- <Icon
3
- :icon="iconName"
4
- :class="['vc-fa-icon', !hasCustomSize && `vc-fa-icon--${size}`, variant ? `vc-fa-icon--${variant}` : '']"
2
+ <i
3
+ :class="[
4
+ 'vc-fa-icon',
5
+ !hasCustomSize && `vc-fa-icon--${size}`,
6
+ iconClasses,
7
+ variant ? `vc-fa-icon--${variant}` : '',
8
+ ]"
5
9
  :style="mergedStyle"
6
10
  aria-hidden="true"
7
- />
11
+ ></i>
8
12
  </template>
9
13
 
10
14
  <script lang="ts" setup>
11
15
  import { computed } from "vue";
12
- import { Icon } from "@iconify/vue";
13
16
  import type { IconSize, IconVariant } from "./types";
14
17
  import { useIcon } from "./composables";
15
18
 
@@ -62,40 +65,31 @@ const mergedStyle = computed(() => {
62
65
  return styles;
63
66
  });
64
67
 
65
- const iconName = computed(() => {
66
- const iconParts = props.icon.split(" ");
67
- let style = "solid";
68
- let name = props.icon;
69
-
70
- if (iconParts.length > 1) {
71
- const prefix = iconParts[0];
72
- name = iconParts.slice(1).join(" ").replace("fa-", "");
73
-
74
- switch (prefix) {
75
- case "fas":
76
- style = "solid";
77
- break;
78
- case "far":
79
- style = "regular";
80
- break;
81
- case "fab":
82
- style = "brands";
83
- break;
84
- case "fal":
85
- style = "light";
86
- break;
87
- case "fad":
88
- style = "duotone";
89
- break;
90
- default:
91
- // Use the already processed name from slice(1), not the original props.icon
92
- break;
93
- }
94
- } else {
95
- name = name.replace("fa-", "");
68
+ // Compute the FontAwesome class from the icon string
69
+ const iconClasses = computed(() => {
70
+ // If icon already has a style prefix (fas, far, fal, fab), use it as is
71
+ if (
72
+ props.icon.startsWith("fas ") ||
73
+ props.icon.startsWith("far ") ||
74
+ props.icon.startsWith("fal ") ||
75
+ props.icon.startsWith("fab ") ||
76
+ props.icon.startsWith("fad ")
77
+ ) {
78
+ return props.icon;
96
79
  }
97
80
 
98
- return `fa-${style}:${name}`;
81
+ // If icon starts with fa-, add the default style prefix
82
+ if (props.icon.startsWith("fa-")) {
83
+ return `fas ${props.icon}`;
84
+ }
85
+
86
+ // If icon contains fa- but doesn't start with a style prefix, assume the style prefix is included
87
+ if (props.icon.includes("fa-")) {
88
+ return props.icon;
89
+ }
90
+
91
+ // Otherwise, assume it's a bare icon name and add both the style prefix and fa- prefix
92
+ return `fas fa-${props.icon}`;
99
93
  });
100
94
  </script>
101
95
 
@@ -110,6 +110,16 @@ const sizeMap = {
110
110
  xxxl: 64,
111
111
  } as const;
112
112
 
113
+ // Scaling factors for different icon types to make them visually equal
114
+ const scalingFactors = {
115
+ fontawesome: 1, // base reference
116
+ material: 1.1,
117
+ bootstrap: 0.95,
118
+ lucide: 1.2,
119
+ custom: 1,
120
+ svg: 1,
121
+ };
122
+
113
123
  // Function to detect icon type if not explicitly specified
114
124
  const detectIconType = computed((): IconType => {
115
125
  if (typeof props.icon !== "string") {
@@ -152,16 +162,16 @@ const normalizedIconName = computed(() => {
152
162
  return props.icon;
153
163
  }
154
164
 
155
- const type = detectIconType.value;
165
+ if (detectIconType.value === "material" && props.icon.startsWith("material-")) {
166
+ return props.icon.replace(/^material-/, "");
167
+ }
156
168
 
157
- switch (type) {
158
- case "material":
159
- return props.icon.replace(/^material-/, "");
160
- case "lucide":
161
- return props.icon.replace(/^lucide-/, "");
162
- default:
163
- return props.icon;
169
+ if (detectIconType.value === "lucide" && props.icon.startsWith("lucide-")) {
170
+ const baseName = props.icon.replace(/^lucide-/, "");
171
+ return baseName.endsWith("Icon") ? baseName : `${baseName}Icon`;
164
172
  }
173
+
174
+ return props.icon;
165
175
  });
166
176
 
167
177
  // Check if icon is a Material Design symbol
@@ -176,7 +186,7 @@ const isLucideIcon = computed(() => detectIconType.value === "lucide");
176
186
  // Check if icon is a Font Awesome Icon
177
187
  const isFontAwesomeIcon = computed(() => detectIconType.value === "fontawesome");
178
188
 
179
- // Check if icon is a component or can be resolved as a component
189
+ // Check if the icon is a component or can be resolved as a component
180
190
  const isCustomIcon = computed(() => {
181
191
  if (typeof props.icon !== "string") {
182
192
  return true; // Component instance passed directly
@@ -191,13 +201,25 @@ const isCustomIcon = computed(() => {
191
201
  return false;
192
202
  }
193
203
 
194
- // For Lucide icons, assume they can be resolved (safer approach)
204
+ // Check for Lucide icons - they could be resolved as components
195
205
  if (isLucideIcon.value) {
196
- return true; // Assume Lucide icons are available as components
206
+ try {
207
+ const iconName =
208
+ typeof normalizedIconName.value === "string" ? normalizedIconName.value : String(normalizedIconName.value);
209
+ const resolved = resolveComponent(iconName);
210
+ return resolved !== iconName;
211
+ } catch (e) {
212
+ return false;
213
+ }
197
214
  }
198
215
 
199
- // For other cases, assume it's not a component to avoid resolveComponent calls
200
- return false;
216
+ // Check if string is a component name that can be resolved
217
+ try {
218
+ const resolved = resolveComponent(props.icon);
219
+ return resolved !== props.icon; // If resolved is different from original string, it's a component name
220
+ } catch (e) {
221
+ return false;
222
+ }
201
223
  });
202
224
 
203
225
  // Get the component instance for rendering
@@ -211,14 +233,15 @@ const safeIcon = computed(() => {
211
233
  return "i";
212
234
  }
213
235
 
214
- // For Lucide icons, return the normalized name directly
215
- if (isLucideIcon.value) {
236
+ // Try to resolve component by name (mostly for Lucide icons)
237
+ try {
216
238
  const iconName =
217
239
  typeof normalizedIconName.value === "string" ? normalizedIconName.value : String(normalizedIconName.value);
218
- return iconName; // Let Vue handle component resolution in template
240
+ const resolved = resolveComponent(iconName);
241
+ return resolved !== iconName ? resolved : "i"; // Return resolved component or fallback to 'i'
242
+ } catch (e) {
243
+ return "i";
219
244
  }
220
-
221
- return "i";
222
245
  });
223
246
 
224
247
  // Determine which component to render
@@ -292,32 +315,82 @@ const containerStyle = computed(() => {
292
315
 
293
316
  // Prepare props for the rendered component
294
317
  const componentProps = computed(() => {
295
- const type = detectIconType.value;
296
-
297
- switch (type) {
298
- case "lucide":
299
- case "material":
300
- case "bootstrap":
301
- case "fontawesome":
302
- return { icon: normalizedIconName.value, size: props.size, variant: props.variant, customSize: props.customSize };
303
- case "svg":
304
- return {
305
- icon: svgPath.value,
306
- size: props.size,
307
- variant: props.variant,
308
- strokeWidth: 1.5,
309
- customSize: props.customSize, // Set custom size without scaling
310
- basePath: props.basePath,
311
- };
312
- default:
313
- // For custom components, no extra props needed
314
- return {
315
- size: props.size,
316
- width: calculatedSize.value,
317
- height: calculatedSize.value,
318
- color: props.variant ? `var(--icon-color-${props.variant})` : "currentColor",
319
- };
318
+ if (isMaterialIcon.value) {
319
+ // Clean the name from possible suffixes of the icon type
320
+ const cleanIconName =
321
+ typeof normalizedIconName.value === "string"
322
+ ? normalizedIconName.value.replace(/-outlined$|-rounded$|-sharp$/, "")
323
+ : normalizedIconName.value;
324
+
325
+ return {
326
+ icon: cleanIconName,
327
+ size: props.size,
328
+ variant: props.variant,
329
+ type: "outlined",
330
+ fill: 0,
331
+ weight: 300,
332
+ grade: 0,
333
+ customSize: props.customSize, // Set custom size without scaling
334
+ };
335
+ }
336
+
337
+ if (isBootstrapIcon.value) {
338
+ // For Bootstrap icons
339
+ const iconName = typeof props.icon === "string" ? props.icon : "";
340
+
341
+ return {
342
+ icon: iconName,
343
+ size: props.size,
344
+ variant: props.variant,
345
+ customSize: props.customSize, // Set custom size without scaling
346
+ };
347
+ }
348
+
349
+ if (isLucideIcon.value) {
350
+ // For Lucide icons
351
+ const iconName =
352
+ typeof normalizedIconName.value === "string" ? normalizedIconName.value : String(normalizedIconName.value);
353
+
354
+ return {
355
+ icon: iconName,
356
+ size: props.size,
357
+ variant: props.variant,
358
+ strokeWidth: 1.5,
359
+ customSize: props.customSize, // Set custom size without scaling
360
+ };
361
+ }
362
+
363
+ if (isFontAwesomeIcon.value) {
364
+ // For Font Awesome icons
365
+ return {
366
+ icon: typeof props.icon === "string" ? props.icon : "",
367
+ size: props.size,
368
+ variant: props.variant,
369
+ customSize: props.customSize, // Set custom size without scaling
370
+ };
371
+ }
372
+
373
+ if (isSvgIcon.value) {
374
+ return {
375
+ icon: svgPath.value,
376
+ size: props.size,
377
+ variant: props.variant,
378
+ strokeWidth: 1.5,
379
+ customSize: props.customSize, // Set custom size without scaling
380
+ basePath: props.basePath,
381
+ };
320
382
  }
383
+
384
+ if (isCustomIcon.value) {
385
+ return {
386
+ size: props.size,
387
+ width: calculatedSize.value,
388
+ height: calculatedSize.value,
389
+ color: props.variant ? `var(--icon-color-${props.variant})` : "currentColor",
390
+ };
391
+ }
392
+
393
+ return {};
321
394
  });
322
395
 
323
396
  // Check if icon is an SVG icon
@@ -461,4 +534,74 @@ const svgPath = computed(() => {
461
534
  min-height: calc(var(--icon-size-xxxl) * 1.33);
462
535
  font-size: var(--icon-size-xxxl); /* Set font-size for the container */
463
536
  }
537
+
538
+ /* Material Icons specific styles */
539
+ .material-symbols-outlined,
540
+ .material-symbols-rounded,
541
+ .material-symbols-sharp {
542
+ font-family: "Material Symbols Outlined";
543
+ font-weight: normal;
544
+ font-style: normal;
545
+ line-height: 1;
546
+ letter-spacing: normal;
547
+ text-transform: none;
548
+ white-space: nowrap;
549
+ word-wrap: normal;
550
+ direction: ltr;
551
+ font-feature-settings: "liga";
552
+ -webkit-font-feature-settings: "liga";
553
+ -webkit-font-smoothing: antialiased;
554
+ }
555
+
556
+ /* Bootstrap Icons specific styles */
557
+ [class^="bi-"],
558
+ [class*=" bi-"] {
559
+ font-family: bootstrap-icons !important;
560
+ font-style: normal;
561
+ font-weight: normal !important;
562
+ font-variant: normal;
563
+ text-transform: none;
564
+ line-height: 1;
565
+ -webkit-font-smoothing: antialiased;
566
+ -moz-osx-font-smoothing: grayscale;
567
+ }
568
+
569
+ /* Font Awesome Icons */
570
+ [class^="fa-"],
571
+ [class*=" fa-"],
572
+ [class~="fas"],
573
+ [class~="far"],
574
+ [class~="fal"],
575
+ [class~="fab"],
576
+ [class~="fad"] {
577
+ font-family: "Font Awesome 6 Free", "Font Awesome 6 Brands", "Font Awesome 6 Duotone", "Font Awesome 6 Pro";
578
+ font-style: normal;
579
+ font-weight: normal;
580
+ font-variant: normal;
581
+ text-transform: none;
582
+ line-height: 1;
583
+ -webkit-font-smoothing: antialiased;
584
+ -moz-osx-font-smoothing: grayscale;
585
+ }
586
+
587
+ /* Font weight adjustments for Font Awesome */
588
+ .fas,
589
+ .fa-solid {
590
+ font-weight: 900;
591
+ }
592
+
593
+ .far,
594
+ .fa-regular {
595
+ font-weight: 400;
596
+ }
597
+
598
+ .fal,
599
+ .fa-light {
600
+ font-weight: 300;
601
+ }
602
+
603
+ .fab,
604
+ .fa-brands {
605
+ font-family: "Font Awesome 6 Brands";
606
+ }
464
607
  </style>
@@ -1,6 +1,7 @@
1
1
  <template>
2
- <Icon
3
- :icon="iconName"
2
+ <component
3
+ :is="resolvedIconComponent"
4
+ v-if="resolvedIconComponent"
4
5
  :class="[
5
6
  'vc-lucide-icon',
6
7
  !hasCustomSize && `vc-lucide-icon--${size}`,
@@ -9,12 +10,18 @@
9
10
  :style="computedStyle"
10
11
  aria-hidden="true"
11
12
  />
13
+ <span
14
+ v-else
15
+ :class="['vc-lucide-icon', !hasCustomSize && `vc-lucide-icon--${size}`]"
16
+ >
17
+ <i class="vc-lucide-icon__fallback">❓</i>
18
+ </span>
12
19
  </template>
13
20
 
14
21
  <script lang="ts" setup>
15
- import { computed } from "vue";
16
- import { Icon } from "@iconify/vue";
22
+ import { computed, markRaw, onMounted, ref } from "vue";
17
23
  import type { IconSize, IconVariant } from "./types";
24
+ import type { Component } from "vue";
18
25
  import { useIcon } from "./composables";
19
26
 
20
27
  interface Props {
@@ -33,6 +40,11 @@ interface Props {
33
40
  */
34
41
  variant?: IconVariant;
35
42
 
43
+ /**
44
+ * Stroke width for SVG
45
+ */
46
+ strokeWidth?: number;
47
+
36
48
  /**
37
49
  * Custom size in pixels
38
50
  */
@@ -41,11 +53,15 @@ interface Props {
41
53
 
42
54
  const props = withDefaults(defineProps<Props>(), {
43
55
  size: "m",
56
+ strokeWidth: 1.5,
44
57
  });
45
58
 
46
59
  // Check if using custom size to conditionally apply CSS class
47
60
  const hasCustomSize = computed(() => typeof props.customSize === "number" && props.customSize > 0);
48
61
 
62
+ // Component reference for Lucide icon
63
+ const resolvedIconComponent = ref<Component | null>(null);
64
+
49
65
  // Use the shared icon composable for consistent scaling
50
66
  const { iconStyle } = useIcon({
51
67
  type: "lucide",
@@ -54,40 +70,71 @@ const { iconStyle } = useIcon({
54
70
  customSize: props.customSize,
55
71
  });
56
72
 
73
+ // Normalize the icon name for Lucide
74
+ const normalizedIconName = computed(() => {
75
+ if (!props.icon) return "HelpCircleIcon";
76
+
77
+ // Handle removal of lucide- prefix
78
+ let name = props.icon;
79
+ if (name.startsWith("lucide-")) {
80
+ name = name.substring(7);
81
+ }
82
+
83
+ // Ensure name ends with 'Icon'
84
+ if (!name.endsWith("Icon")) {
85
+ name = `${name}Icon`;
86
+ }
87
+
88
+ // Convert to PascalCase if kebab-case
89
+ if (name.includes("-")) {
90
+ name = name
91
+ .split("-")
92
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
93
+ .join("");
94
+ }
95
+
96
+ // Ensure first letter is capitalized
97
+ return name.charAt(0).toUpperCase() + name.slice(1);
98
+ });
99
+
57
100
  // Combine the shared icon styles with Lucide-specific settings
58
101
  const computedStyle = computed(() => {
59
102
  const styles = { ...iconStyle.value };
60
103
 
104
+ if (props.strokeWidth) {
105
+ styles.strokeWidth = props.strokeWidth.toString();
106
+ }
107
+
61
108
  // If using custom size, make sure size is applied with !important
62
- // if (hasCustomSize.value) {
63
- // if (styles.width) styles.width = `${styles.width.replace("px", "")}px !important`;
64
- // if (styles.height) styles.height = `${styles.height.replace("px", "")}px !important`;
65
- // }
109
+ if (hasCustomSize.value) {
110
+ if (styles.width) styles.width = `${styles.width.replace("px", "")}px !important`;
111
+ if (styles.height) styles.height = `${styles.height.replace("px", "")}px !important`;
112
+ }
66
113
 
67
114
  return styles;
68
115
  });
69
116
 
70
- const iconName = computed(() => {
71
- let name = props.icon;
72
-
73
- // Convert from PascalCaseIcon or kebab-caseIcon to kebab-case.
74
- name = name.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase();
75
-
76
- if (name.endsWith("-icon")) {
77
- name = name.slice(0, -5);
117
+ // Dynamically import the Lucide icon
118
+ onMounted(async () => {
119
+ try {
120
+ // Import from lucide-vue-next
121
+ const module = await import("lucide-vue-next");
122
+
123
+ // Get the icon component safely with type checking
124
+ const iconName = normalizedIconName.value;
125
+ if (module && typeof module === "object" && iconName in module) {
126
+ resolvedIconComponent.value = markRaw(module[iconName as keyof typeof module] as Component);
127
+ } else {
128
+ console.warn(`Lucide icon not found: ${iconName}`);
129
+ }
130
+ } catch (error) {
131
+ console.error(`Error loading Lucide icon: ${normalizedIconName.value}`, error);
78
132
  }
79
-
80
- return `lucide:${name}`;
81
133
  });
82
134
  </script>
83
135
 
84
136
  <style lang="scss">
85
137
  .vc-lucide-icon {
86
- & g,
87
- & path {
88
- stroke-width: var(--vc-lucide-icon-stroke-width, 1.5);
89
- }
90
-
91
138
  display: inline-flex;
92
139
  align-items: center;
93
140
  justify-content: center;