@alphacifer/slidev-addon-theme 0.0.4 → 0.0.6

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 (84) hide show
  1. package/README.md +127 -2
  2. package/components/arc-arrow-process/ArcArrowProcess.vue +361 -0
  3. package/components/arc-arrow-process/ArcArrowProcessArrow.vue +143 -0
  4. package/components/arc-arrow-process/ArcArrowProcessCallout.vue +361 -0
  5. package/components/arc-arrow-process/ArcArrowProcessContent.vue +38 -0
  6. package/components/arc-arrow-process/ArcArrowProcessHeading.vue +43 -0
  7. package/components/arc-arrow-process/ArcArrowProcessIcon.vue +154 -0
  8. package/components/arc-compare/ArcCompare.vue +443 -0
  9. package/components/arc-compare/ArcCompareBadge.vue +63 -0
  10. package/components/arc-compare/ArcCompareCallout.vue +386 -0
  11. package/components/arc-compare/ArcCompareContent.vue +52 -0
  12. package/components/arc-compare/ArcCompareContents.vue +18 -0
  13. package/components/arc-compare/ArcCompareHeading.vue +52 -0
  14. package/components/arc-compare/ArcCompareLeft.vue +183 -0
  15. package/components/arc-compare/ArcCompareRight.vue +183 -0
  16. package/components/arc-compare/ArcCompareTitle.vue +47 -0
  17. package/components/arrow-triad/ArrowTriad.vue +368 -0
  18. package/components/arrow-triad/ArrowTriadCallout.vue +300 -0
  19. package/components/arrow-triad/ArrowTriadContent.vue +38 -0
  20. package/components/arrow-triad/ArrowTriadHeading.vue +38 -0
  21. package/components/arrow-triad/ArrowTriadIcon.vue +96 -0
  22. package/components/chevron-card/ChevronCard.vue +237 -0
  23. package/components/chevron-card/ChevronCardBadge.vue +74 -0
  24. package/components/chevron-card/ChevronCardContent.vue +39 -0
  25. package/components/chevron-card/ChevronCardHeading.vue +39 -0
  26. package/components/hex-triad/HexTriad.vue +438 -0
  27. package/components/hex-triad/HexTriadBadge.vue +110 -0
  28. package/components/hex-triad/HexTriadCallout.vue +200 -0
  29. package/components/hex-triad/HexTriadCenterBadge.vue +51 -0
  30. package/components/hex-triad/HexTriadContent.vue +20 -0
  31. package/components/hex-triad/HexTriadHeading.vue +28 -0
  32. package/components/hex-triad/HexTriadLayer.vue +28 -0
  33. package/components/horiz-card/HorizCard.vue +318 -0
  34. package/components/horiz-card/HorizCardBadge.vue +75 -0
  35. package/components/horiz-card/HorizCardContent.vue +46 -0
  36. package/components/horiz-card/HorizCardHeading.vue +47 -0
  37. package/components/horiz-card/HorizCardIcon.vue +118 -0
  38. package/components/quad-hub/QuadHub.vue +642 -0
  39. package/components/quad-hub/QuadHubCallout.vue +267 -0
  40. package/components/quad-hub/QuadHubCenter.vue +53 -0
  41. package/components/quad-hub/QuadHubContent.vue +39 -0
  42. package/components/quad-hub/QuadHubHeading.vue +44 -0
  43. package/components/quad-hub/QuadHubIcon.vue +112 -0
  44. package/components/shifting-heading/TransitionHeading.vue +3 -3
  45. package/components/vert-card/VertCard.vue +389 -0
  46. package/components/vert-card/VertCardBadge.vue +72 -0
  47. package/components/vert-card/VertCardContent.vue +45 -0
  48. package/components/vert-card/VertCardHeading.vue +68 -0
  49. package/components/vert-card/VertCardIcon.vue +126 -0
  50. package/components/vert-card/VertCardTitle.vue +76 -0
  51. package/layouts/core/arc-toc.vue +76 -113
  52. package/layouts/core/bg-center.vue +32 -4
  53. package/layouts/shifting-heading/shifting-intro.vue +35 -7
  54. package/package.json +3 -2
  55. package/utils/arcArrowProcess/context.ts +28 -0
  56. package/utils/arcArrowProcess/createArcArrowProcessGeometry.ts +137 -0
  57. package/utils/arcArrowProcess/index.ts +3 -0
  58. package/utils/arcArrowProcess/resolveArcArrowProcessItems.ts +189 -0
  59. package/utils/arcCompare/context.ts +36 -0
  60. package/utils/arcCompare/createArcCompareGeometry.ts +254 -0
  61. package/utils/arcCompare/index.ts +3 -0
  62. package/utils/arcCompare/resolveArcCompareItems.ts +123 -0
  63. package/utils/arrowTriad/context.ts +38 -0
  64. package/utils/arrowTriad/createArrowTriadGeometry.ts +63 -0
  65. package/utils/arrowTriad/index.ts +3 -0
  66. package/utils/arrowTriad/resolveArrowTriadItems.ts +121 -0
  67. package/utils/chevronCard/context.ts +12 -0
  68. package/utils/chevronCard/createChevronCardGeometry.ts +48 -0
  69. package/utils/chevronCard/index.ts +2 -0
  70. package/utils/hexTriad/context.ts +25 -0
  71. package/utils/hexTriad/createHexTriadGeometry.ts +182 -0
  72. package/utils/hexTriad/index.ts +3 -0
  73. package/utils/hexTriad/resolveHexTriadItems.ts +63 -0
  74. package/utils/horizCard/context.ts +13 -0
  75. package/utils/horizCard/createHorizCardGeometry.ts +243 -0
  76. package/utils/horizCard/index.ts +2 -0
  77. package/utils/quadHub/context.ts +26 -0
  78. package/utils/quadHub/createQuadHubGeometry.ts +331 -0
  79. package/utils/quadHub/index.ts +3 -0
  80. package/utils/quadHub/resolveQuadHubItems.ts +214 -0
  81. package/utils/useDiagramAutoScale.ts +300 -0
  82. package/utils/vertCard/context.ts +18 -0
  83. package/utils/vertCard/createVertCardGeometry.ts +242 -0
  84. package/utils/vertCard/index.ts +2 -0
@@ -0,0 +1,389 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ type ComputedRef,
4
+ computed,
5
+ Fragment,
6
+ provide,
7
+ useSlots,
8
+ type VNode,
9
+ } from 'vue';
10
+
11
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
12
+ import {
13
+ createVertCardBottomBlobPath,
14
+ createVertCardTopRightBlobPath,
15
+ hexToRgba,
16
+ type TVertCardBadgeVariant,
17
+ VERT_CARD_KEY,
18
+ } from '../../utils/vertCard';
19
+
20
+ defineOptions({
21
+ name: 'VertCard',
22
+ inheritAttrs: false,
23
+ });
24
+
25
+ export interface IVertCardProps {
26
+ readonly color?: string;
27
+ readonly dots?: boolean;
28
+ readonly title?: string;
29
+ readonly cardBg?: string;
30
+ readonly textColor?: string;
31
+ readonly titleColor?: string;
32
+ readonly lightColor?: string;
33
+ readonly stripeColor?: string;
34
+ readonly description?: string;
35
+ readonly hasDivider?: boolean;
36
+ readonly step?: string | number;
37
+ readonly icon?: string | object;
38
+ readonly hasIconCircle?: boolean;
39
+ /**
40
+ * Controls the badge display mode:
41
+ * - 'outside': ribbon badge anchored to the outer left border (default)
42
+ * - 'inside': badge displayed inside the card body (future state)
43
+ * - 'none' | 'hidden': badge is completely hidden
44
+ */
45
+ readonly variant?: TVertCardBadgeVariant;
46
+ readonly badgeVariant?: TVertCardBadgeVariant;
47
+ }
48
+
49
+ const props = withDefaults(defineProps<IVertCardProps>(), {
50
+ dots: true,
51
+ step: '01',
52
+ icon: undefined,
53
+ hasDivider: true,
54
+ title: undefined,
55
+ color: '#ea583a',
56
+ cardBg: '#ffffff',
57
+ variant: undefined,
58
+ textColor: undefined,
59
+ lightColor: undefined,
60
+ titleColor: undefined,
61
+ stripeColor: undefined,
62
+ description: undefined,
63
+ badgeVariant: 'outside',
64
+ hasIconCircle: undefined,
65
+ });
66
+
67
+ const slots = useSlots();
68
+
69
+ interface INamedComponent {
70
+ readonly name?: string;
71
+ }
72
+
73
+ function hasComponentName(type: unknown, name: string): boolean {
74
+ if (typeof type === 'object' && type !== null && 'name' in type) {
75
+ return (type as INamedComponent).name === name;
76
+ }
77
+
78
+ return false;
79
+ }
80
+
81
+ function flattenVNodes(nodes: VNode[]): VNode[] {
82
+ const result: VNode[] = [];
83
+
84
+ for (const node of nodes) {
85
+ if (node.type === Fragment && Array.isArray(node.children)) {
86
+ result.push(...flattenVNodes(node.children as VNode[]));
87
+ } else {
88
+ result.push(node);
89
+ }
90
+ }
91
+
92
+ return result;
93
+ }
94
+
95
+ function isBadgeNode(node: VNode): boolean {
96
+ return hasComponentName(node.type, 'VertCardBadge');
97
+ }
98
+
99
+ function isIconNode(node: VNode): boolean {
100
+ return hasComponentName(node.type, 'VertCardIcon');
101
+ }
102
+
103
+ function isTitleNode(node: VNode): boolean {
104
+ return (
105
+ hasComponentName(node.type, 'VertCardTitle') ||
106
+ hasComponentName(node.type, 'VertCardHeading')
107
+ );
108
+ }
109
+
110
+ function isContentNode(node: VNode): boolean {
111
+ return hasComponentName(node.type, 'VertCardContent');
112
+ }
113
+
114
+ const defaultNodes = computed(() => {
115
+ if (!slots.default) {
116
+ return [];
117
+ }
118
+
119
+ return flattenVNodes(slots.default());
120
+ });
121
+
122
+ const slottedBadge = computed(() => {
123
+ return defaultNodes.value.find(isBadgeNode);
124
+ });
125
+
126
+ const slottedIcon = computed(() => {
127
+ return defaultNodes.value.find(isIconNode);
128
+ });
129
+
130
+ const slottedTitle = computed(() => {
131
+ return defaultNodes.value.find(isTitleNode);
132
+ });
133
+
134
+ const slottedContent = computed(() => {
135
+ return defaultNodes.value.find(isContentNode);
136
+ });
137
+
138
+ const otherSlottedNodes = computed(() => {
139
+ return defaultNodes.value.filter((node) => {
140
+ return (
141
+ !isBadgeNode(node) &&
142
+ !isIconNode(node) &&
143
+ !isTitleNode(node) &&
144
+ !isContentNode(node)
145
+ );
146
+ });
147
+ });
148
+
149
+ const resolvedColor = computed(() => {
150
+ return props.color;
151
+ });
152
+
153
+ const resolvedLightColor = computed(() => {
154
+ return (
155
+ props.lightColor ??
156
+ hexToRgba({
157
+ alpha: 0.16,
158
+ hex: props.color,
159
+ })
160
+ );
161
+ });
162
+
163
+ const resolvedStripeDarkColor = computed(() => {
164
+ return props.stripeColor ?? props.color;
165
+ });
166
+
167
+ const resolvedStripeLightColor = computed(() => {
168
+ return hexToRgba({
169
+ alpha: 0.55,
170
+ hex: props.color,
171
+ });
172
+ });
173
+
174
+ const resolvedCardBg = computed(() => {
175
+ return props.cardBg;
176
+ });
177
+
178
+ const resolvedTitleColor = computed(() => {
179
+ return props.titleColor ?? props.color;
180
+ });
181
+
182
+ const resolvedTextColor = computed(() => {
183
+ return props.textColor;
184
+ });
185
+
186
+ const resolvedStep = computed(() => {
187
+ return props.step;
188
+ });
189
+
190
+ const resolvedBadgeVariant = computed<TVertCardBadgeVariant>(() => {
191
+ return props.variant ?? props.badgeVariant ?? 'outside';
192
+ });
193
+
194
+ const isBadgeVisible = computed(() => {
195
+ return (
196
+ resolvedBadgeVariant.value !== 'none' &&
197
+ resolvedBadgeVariant.value !== 'hidden'
198
+ );
199
+ });
200
+
201
+ const isBadgeOutside = computed(() => {
202
+ return resolvedBadgeVariant.value === 'outside';
203
+ });
204
+
205
+ provide(VERT_CARD_KEY, {
206
+ step: resolvedStep,
207
+ color: resolvedColor,
208
+ cardBg: resolvedCardBg,
209
+ lightColor: resolvedLightColor,
210
+ titleColor: resolvedTitleColor,
211
+ badgeVariant: resolvedBadgeVariant,
212
+ stripeColor: resolvedStripeDarkColor,
213
+ hasDivider: computed(() => props.hasDivider),
214
+ textColor: resolvedTextColor as ComputedRef<string | undefined>,
215
+ });
216
+
217
+ const BLOB_WIDTH = 128;
218
+ const BLOB_HEIGHT = 112;
219
+
220
+ const bottomBlobPath = computed(() => {
221
+ return createVertCardBottomBlobPath({
222
+ blobHeight: 96,
223
+ blobWidth: 128,
224
+ borderRadius: 21,
225
+ width: BLOB_WIDTH,
226
+ height: BLOB_HEIGHT,
227
+ });
228
+ });
229
+
230
+ const topRightInnerBlobPath = computed(() => {
231
+ return createVertCardTopRightBlobPath({
232
+ blobHeight: 96,
233
+ blobWidth: 128,
234
+ borderRadius: 21,
235
+ width: BLOB_WIDTH,
236
+ height: BLOB_HEIGHT,
237
+ });
238
+ });
239
+
240
+ const topRightOuterBlobPath = computed(() => {
241
+ return createVertCardTopRightBlobPath({
242
+ blobHeight: 76,
243
+ blobWidth: 104,
244
+ borderRadius: 21,
245
+ width: BLOB_WIDTH,
246
+ height: BLOB_HEIGHT,
247
+ });
248
+ });
249
+
250
+ // Determine whether icon circle is active (optional icon)
251
+ const isIconActive = computed(() => {
252
+ if (props.hasIconCircle !== undefined) {
253
+ return props.hasIconCircle;
254
+ }
255
+
256
+ return Boolean(props.icon || slots.icon || slottedIcon.value !== undefined);
257
+ });
258
+
259
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
260
+ 'alpha-vert-card relative inline-flex flex-col select-none rounded-[24px] border-[2.75px] border-solid font-sans',
261
+ );
262
+ </script>
263
+
264
+ <template>
265
+ <div
266
+ v-bind="forwardedAttrs()"
267
+ :class="className()"
268
+ :style="{
269
+ borderColor: resolvedColor,
270
+ backgroundColor: resolvedCardBg,
271
+ }"
272
+ >
273
+ <!-- Clipped decorative accent layers (responsive and symmetrical in opposite corners) -->
274
+ <div
275
+ class="absolute inset-0 rounded-[21.25px] overflow-hidden pointer-events-none"
276
+ >
277
+ <!-- Bottom-left organic wave blob (kept as requested) -->
278
+ <svg
279
+ class="absolute bottom-0 left-0 w-32 h-28 pointer-events-none overflow-visible"
280
+ viewBox="0 0 128 112"
281
+ xmlns="http://www.w3.org/2000/svg"
282
+ >
283
+ <path
284
+ class="alpha-vert-card__blob"
285
+ :d="bottomBlobPath"
286
+ :fill="resolvedColor"
287
+ />
288
+ </svg>
289
+ <!-- Top-right corner organic wave blob (symmetrical with bottom-left) -->
290
+ <svg
291
+ class="absolute top-0 right-0 w-32 h-28 pointer-events-none overflow-visible"
292
+ viewBox="0 0 128 112"
293
+ xmlns="http://www.w3.org/2000/svg"
294
+ >
295
+ <!-- Inner diagonal wave band (lighter tone) -->
296
+ <path
297
+ class="alpha-vert-card__blob-top-inner"
298
+ :d="topRightInnerBlobPath"
299
+ :fill="resolvedStripeLightColor"
300
+ />
301
+ <!-- Outer diagonal wave accent (accent tone) -->
302
+ <path
303
+ class="alpha-vert-card__blob-top-outer"
304
+ :d="topRightOuterBlobPath"
305
+ :fill="resolvedStripeDarkColor"
306
+ />
307
+ </svg>
308
+ </div>
309
+ <!-- Ribbon Step Badge on left edge (pointing right into the card) -->
310
+ <div
311
+ v-if="isBadgeOutside && isBadgeVisible"
312
+ class="absolute -left-[20px] z-10"
313
+ :class="isIconActive ? 'top-[72px]' : 'top-[60px]'"
314
+ >
315
+ <slot name="badge">
316
+ <component :is="slottedBadge" v-if="slottedBadge" />
317
+ <VertCardBadge v-else :step="resolvedStep" :color="resolvedColor" />
318
+ </slot>
319
+ </div>
320
+ <!-- Card Content Layer -->
321
+ <div
322
+ class="relative z-10 w-full h-full pt-8 pb-5 px-6 flex flex-col justify-between items-center"
323
+ >
324
+ <!-- Optional Icon Circle (rendered when icon/slot is provided) -->
325
+ <template v-if="isIconActive">
326
+ <div class="flex items-center justify-center flex-shrink-0 mb-3">
327
+ <slot name="icon">
328
+ <component :is="slottedIcon" v-if="slottedIcon" />
329
+ <VertCardIcon
330
+ v-else
331
+ :icon="props.icon"
332
+ :color="resolvedColor"
333
+ :light-color="resolvedLightColor"
334
+ :size="108"
335
+ />
336
+ </slot>
337
+ </div>
338
+ </template>
339
+ <!-- Title, Divider, and Content Description -->
340
+ <div
341
+ class="flex flex-col items-center justify-center flex-1 w-full min-w-0"
342
+ :class="{ 'my-auto py-4': !isIconActive }"
343
+ >
344
+ <slot name="title">
345
+ <component :is="slottedTitle" v-if="slottedTitle" />
346
+ <VertCardTitle
347
+ v-else-if="props.title"
348
+ :color="resolvedTitleColor"
349
+ :has-divider="props.hasDivider"
350
+ >
351
+ {{ props.title }}
352
+ </VertCardTitle>
353
+ </slot>
354
+ <slot name="description">
355
+ <component :is="slottedContent" v-if="slottedContent" />
356
+ <VertCardContent
357
+ v-else-if="props.description"
358
+ :color="resolvedTextColor"
359
+ >
360
+ {{ props.description }}
361
+ </VertCardContent>
362
+ </slot>
363
+ <!-- Any other custom slotted components -->
364
+ <component
365
+ :is="node"
366
+ v-for="(node, index) in otherSlottedNodes"
367
+ :key="index"
368
+ />
369
+ </div>
370
+ <!-- Bottom Row: Centered Decorative Indicator Dots -->
371
+ <div v-if="props.dots" class="flex justify-center items-center gap-2 pt-1">
372
+ <slot name="dots">
373
+ <span
374
+ class="w-2.5 h-2.5 rounded-full inline-block"
375
+ :style="{ backgroundColor: resolvedColor }"
376
+ />
377
+ <span
378
+ class="w-2.5 h-2.5 rounded-full inline-block opacity-40"
379
+ :style="{ backgroundColor: resolvedColor }"
380
+ />
381
+ <span
382
+ class="w-2.5 h-2.5 rounded-full inline-block opacity-40"
383
+ :style="{ backgroundColor: resolvedColor }"
384
+ />
385
+ </slot>
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </template>
@@ -0,0 +1,72 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject } from 'vue';
3
+
4
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
5
+ import { createVertCardBadgePath, VERT_CARD_KEY } from '../../utils/vertCard';
6
+
7
+ defineOptions({
8
+ inheritAttrs: false,
9
+ name: 'VertCardBadge',
10
+ });
11
+
12
+ export interface IVertCardBadgeProps {
13
+ /**
14
+ * Badge fill color. Overrides context color.
15
+ */
16
+ readonly color?: string;
17
+ /**
18
+ * Badge text or step number. Overrides context step.
19
+ */
20
+ readonly step?: string | number;
21
+ }
22
+
23
+ const props = withDefaults(defineProps<IVertCardBadgeProps>(), {
24
+ step: undefined,
25
+ color: undefined,
26
+ });
27
+
28
+ const context = inject(VERT_CARD_KEY, undefined);
29
+
30
+ const resolvedColor = computed(() => {
31
+ return props.color ?? context?.color.value ?? '#ea583a';
32
+ });
33
+
34
+ const resolvedStep = computed(() => {
35
+ return props.step ?? context?.step.value ?? '01';
36
+ });
37
+
38
+ const badgePath = createVertCardBadgePath({
39
+ width: 62,
40
+ height: 48,
41
+ tipRadius: 6,
42
+ leftRadius: 8,
43
+ sideWidth: 42,
44
+ cornerRadius: 6,
45
+ });
46
+
47
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
48
+ 'alpha-vert-card-badge inline-flex items-center justify-center select-none',
49
+ );
50
+ </script>
51
+
52
+ <template>
53
+ <div v-bind="forwardedAttrs()" :class="className()">
54
+ <svg
55
+ class="w-[62px] h-[48px] overflow-visible drop-shadow-sm"
56
+ viewBox="0 0 62 48"
57
+ xmlns="http://www.w3.org/2000/svg"
58
+ >
59
+ <path :d="badgePath" :fill="resolvedColor" />
60
+ <text
61
+ x="23"
62
+ y="24"
63
+ text-anchor="middle"
64
+ dominant-baseline="central"
65
+ fill="#ffffff"
66
+ class="font-sans font-bold text-[20px] tracking-tight"
67
+ >
68
+ <slot>{{ resolvedStep }}</slot>
69
+ </text>
70
+ </svg>
71
+ </div>
72
+ </template>
@@ -0,0 +1,45 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject } from 'vue';
3
+
4
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
5
+ import { VERT_CARD_KEY } from '../../utils/vertCard';
6
+
7
+ defineOptions({
8
+ inheritAttrs: false,
9
+ name: 'VertCardContent',
10
+ });
11
+
12
+ export interface IVertCardContentProps {
13
+ /**
14
+ * Content text color. Overrides context textColor.
15
+ */
16
+ readonly color?: string;
17
+ }
18
+
19
+ const props = withDefaults(defineProps<IVertCardContentProps>(), {
20
+ color: undefined,
21
+ });
22
+
23
+ const context = inject(VERT_CARD_KEY, undefined);
24
+
25
+ const contentColor = computed(() => {
26
+ return props.color ?? context?.textColor.value ?? undefined;
27
+ });
28
+
29
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
30
+ 'alpha-vert-card-content mt-3 px-2 font-sans text-base leading-relaxed text-slate-600 dark:text-slate-300 opacity-90 text-center [&_p]:text-base [&_p]:m-0',
31
+ );
32
+ </script>
33
+
34
+ <template>
35
+ <div
36
+ v-bind="forwardedAttrs()"
37
+ :class="className()"
38
+ :style="contentColor ? { color: contentColor } : undefined"
39
+ >
40
+ <slot>
41
+ Lorem ipsum dolor sit amet, nibh est. A magna maecenas, quam magna nec
42
+ quis, lorem nunc. Suspendisse viverra sodales mauris, cras pharetra proin.
43
+ </slot>
44
+ </div>
45
+ </template>
@@ -0,0 +1,68 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject } from 'vue';
3
+
4
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
5
+ import { VERT_CARD_KEY } from '../../utils/vertCard';
6
+
7
+ defineOptions({
8
+ inheritAttrs: false,
9
+ name: 'VertCardHeading',
10
+ });
11
+
12
+ export interface IVertCardHeadingProps {
13
+ /**
14
+ * Title text color. Overrides context titleColor.
15
+ */
16
+ readonly color?: string;
17
+ /**
18
+ * Whether to render the decorative horizontal line with center dot below the title.
19
+ * @default true
20
+ */
21
+ readonly hasDivider?: boolean;
22
+ }
23
+
24
+ const props = withDefaults(defineProps<IVertCardHeadingProps>(), {
25
+ color: undefined,
26
+ hasDivider: true,
27
+ });
28
+
29
+ const context = inject(VERT_CARD_KEY, undefined);
30
+
31
+ const headingColor = computed(() => {
32
+ return (
33
+ props.color ??
34
+ context?.titleColor.value ??
35
+ context?.color.value ??
36
+ '#ea583a'
37
+ );
38
+ });
39
+
40
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
41
+ 'alpha-vert-card-heading font-sans font-bold text-xl tracking-tight leading-tight text-center',
42
+ );
43
+ </script>
44
+
45
+ <template>
46
+ <div class="flex flex-col items-center w-full">
47
+ <h3
48
+ v-bind="forwardedAttrs()"
49
+ :class="className()"
50
+ :style="{ color: headingColor }"
51
+ >
52
+ <slot>Lorem Ipsum</slot>
53
+ </h3>
54
+ <div
55
+ v-if="props.hasDivider"
56
+ class="mt-2.5 flex items-center justify-center w-32 relative"
57
+ >
58
+ <div
59
+ class="w-full h-[1.5px]"
60
+ :style="{ backgroundColor: headingColor }"
61
+ />
62
+ <span
63
+ class="absolute w-2 h-2 rounded-full"
64
+ :style="{ backgroundColor: headingColor }"
65
+ />
66
+ </div>
67
+ </div>
68
+ </template>
@@ -0,0 +1,126 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject } from 'vue';
3
+
4
+ import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
5
+ import { hexToRgba, VERT_CARD_KEY } from '../../utils/vertCard';
6
+
7
+ defineOptions({
8
+ inheritAttrs: false,
9
+ name: 'VertCardIcon',
10
+ });
11
+
12
+ export interface IVertCardIconProps {
13
+ /**
14
+ * Optional icon class (e.g. 'i-carbon-search') or component.
15
+ */
16
+ readonly icon?: string | object;
17
+ /**
18
+ * Foreground icon color. Overrides context color.
19
+ */
20
+ readonly color?: string;
21
+ /**
22
+ * Circular background color. Overrides context lightColor.
23
+ */
24
+ readonly lightColor?: string;
25
+ /**
26
+ * Circle size in pixels or CSS string.
27
+ * @default 108
28
+ */
29
+ readonly size?: number | string;
30
+ }
31
+
32
+ const props = withDefaults(defineProps<IVertCardIconProps>(), {
33
+ size: 108,
34
+ icon: undefined,
35
+ color: undefined,
36
+ lightColor: undefined,
37
+ });
38
+
39
+ const context = inject(VERT_CARD_KEY, undefined);
40
+
41
+ const resolvedColor = computed(() => {
42
+ return props.color ?? context?.color.value ?? '#ea583a';
43
+ });
44
+
45
+ const resolvedLightColor = computed(() => {
46
+ if (props.lightColor) {
47
+ return props.lightColor;
48
+ }
49
+
50
+ if (context?.lightColor.value) {
51
+ return context.lightColor.value;
52
+ }
53
+
54
+ return hexToRgba({
55
+ alpha: 0.16,
56
+ hex: resolvedColor.value,
57
+ });
58
+ });
59
+
60
+ const resolvedSize = computed(() => {
61
+ if (typeof props.size === 'number') {
62
+ return `${props.size}px`;
63
+ }
64
+
65
+ return props.size;
66
+ });
67
+
68
+ const isStringIcon = computed(() => typeof props.icon === 'string');
69
+ const isComponentIcon = computed(() => {
70
+ return typeof props.icon === 'object' && props.icon !== null;
71
+ });
72
+
73
+ const { className, forwardedAttrs } = useMergedUnoAttrs(
74
+ 'alpha-vert-card-icon rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-transform duration-300 select-none',
75
+ );
76
+ </script>
77
+
78
+ <template>
79
+ <div
80
+ v-bind="forwardedAttrs()"
81
+ :class="className()"
82
+ :style="{
83
+ width: resolvedSize,
84
+ height: resolvedSize,
85
+ backgroundColor: resolvedLightColor,
86
+ borderColor: resolvedColor,
87
+ color: resolvedColor,
88
+ }"
89
+ >
90
+ <slot>
91
+ <component
92
+ :is="props.icon"
93
+ v-if="isComponentIcon"
94
+ class="text-4xl"
95
+ :style="{ color: resolvedColor }"
96
+ />
97
+ <div
98
+ v-else-if="isStringIcon && props.icon !== 'default'"
99
+ :class="props.icon"
100
+ class="text-4xl"
101
+ :style="{ color: resolvedColor }"
102
+ />
103
+ <!-- Default search icon matching the reference card -->
104
+ <svg
105
+ v-else
106
+ class="w-12 h-12"
107
+ viewBox="0 0 24 24"
108
+ fill="none"
109
+ stroke="currentColor"
110
+ stroke-width="2.5"
111
+ stroke-linecap="round"
112
+ stroke-linejoin="round"
113
+ >
114
+ <circle cx="11" cy="11" r="7" />
115
+ <line
116
+ x1="16.5"
117
+ y1="16.5"
118
+ x2="21"
119
+ y2="21"
120
+ stroke-width="3.2"
121
+ stroke-linecap="round"
122
+ />
123
+ </svg>
124
+ </slot>
125
+ </div>
126
+ </template>