@alphacifer/slidev-addon-theme 0.0.5 → 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.
- package/README.md +127 -2
- package/components/arc-arrow-process/ArcArrowProcess.vue +361 -0
- package/components/arc-arrow-process/ArcArrowProcessArrow.vue +143 -0
- package/components/arc-arrow-process/ArcArrowProcessCallout.vue +361 -0
- package/components/arc-arrow-process/ArcArrowProcessContent.vue +38 -0
- package/components/arc-arrow-process/ArcArrowProcessHeading.vue +43 -0
- package/components/arc-arrow-process/ArcArrowProcessIcon.vue +154 -0
- package/components/arc-compare/ArcCompare.vue +443 -0
- package/components/arc-compare/ArcCompareBadge.vue +63 -0
- package/components/arc-compare/ArcCompareCallout.vue +386 -0
- package/components/arc-compare/ArcCompareContent.vue +52 -0
- package/components/arc-compare/ArcCompareContents.vue +18 -0
- package/components/arc-compare/ArcCompareHeading.vue +52 -0
- package/components/arc-compare/ArcCompareLeft.vue +183 -0
- package/components/arc-compare/ArcCompareRight.vue +183 -0
- package/components/arc-compare/ArcCompareTitle.vue +47 -0
- package/components/arrow-triad/ArrowTriad.vue +368 -0
- package/components/arrow-triad/ArrowTriadCallout.vue +300 -0
- package/components/arrow-triad/ArrowTriadContent.vue +38 -0
- package/components/arrow-triad/ArrowTriadHeading.vue +38 -0
- package/components/arrow-triad/ArrowTriadIcon.vue +96 -0
- package/components/chevron-card/ChevronCard.vue +237 -0
- package/components/chevron-card/ChevronCardBadge.vue +74 -0
- package/components/chevron-card/ChevronCardContent.vue +39 -0
- package/components/chevron-card/ChevronCardHeading.vue +39 -0
- package/components/hex-triad/HexTriad.vue +438 -0
- package/components/hex-triad/HexTriadBadge.vue +110 -0
- package/components/hex-triad/HexTriadCallout.vue +200 -0
- package/components/hex-triad/HexTriadCenterBadge.vue +51 -0
- package/components/hex-triad/HexTriadContent.vue +20 -0
- package/components/hex-triad/HexTriadHeading.vue +28 -0
- package/components/hex-triad/HexTriadLayer.vue +28 -0
- package/components/horiz-card/HorizCard.vue +318 -0
- package/components/horiz-card/HorizCardBadge.vue +75 -0
- package/components/horiz-card/HorizCardContent.vue +46 -0
- package/components/horiz-card/HorizCardHeading.vue +47 -0
- package/components/horiz-card/HorizCardIcon.vue +118 -0
- package/components/quad-hub/QuadHub.vue +642 -0
- package/components/quad-hub/QuadHubCallout.vue +267 -0
- package/components/quad-hub/QuadHubCenter.vue +53 -0
- package/components/quad-hub/QuadHubContent.vue +39 -0
- package/components/quad-hub/QuadHubHeading.vue +44 -0
- package/components/quad-hub/QuadHubIcon.vue +112 -0
- package/components/shifting-heading/TransitionHeading.vue +2 -2
- package/components/vert-card/VertCard.vue +389 -0
- package/components/vert-card/VertCardBadge.vue +72 -0
- package/components/vert-card/VertCardContent.vue +45 -0
- package/components/vert-card/VertCardHeading.vue +68 -0
- package/components/vert-card/VertCardIcon.vue +126 -0
- package/components/vert-card/VertCardTitle.vue +76 -0
- package/layouts/core/arc-toc.vue +76 -113
- package/layouts/core/bg-center.vue +32 -4
- package/layouts/shifting-heading/shifting-intro.vue +35 -7
- package/package.json +3 -2
- package/utils/arcArrowProcess/context.ts +28 -0
- package/utils/arcArrowProcess/createArcArrowProcessGeometry.ts +137 -0
- package/utils/arcArrowProcess/index.ts +3 -0
- package/utils/arcArrowProcess/resolveArcArrowProcessItems.ts +189 -0
- package/utils/arcCompare/context.ts +36 -0
- package/utils/arcCompare/createArcCompareGeometry.ts +254 -0
- package/utils/arcCompare/index.ts +3 -0
- package/utils/arcCompare/resolveArcCompareItems.ts +123 -0
- package/utils/arrowTriad/context.ts +38 -0
- package/utils/arrowTriad/createArrowTriadGeometry.ts +63 -0
- package/utils/arrowTriad/index.ts +3 -0
- package/utils/arrowTriad/resolveArrowTriadItems.ts +121 -0
- package/utils/chevronCard/context.ts +12 -0
- package/utils/chevronCard/createChevronCardGeometry.ts +48 -0
- package/utils/chevronCard/index.ts +2 -0
- package/utils/hexTriad/context.ts +25 -0
- package/utils/hexTriad/createHexTriadGeometry.ts +182 -0
- package/utils/hexTriad/index.ts +3 -0
- package/utils/hexTriad/resolveHexTriadItems.ts +63 -0
- package/utils/horizCard/context.ts +13 -0
- package/utils/horizCard/createHorizCardGeometry.ts +243 -0
- package/utils/horizCard/index.ts +2 -0
- package/utils/quadHub/context.ts +26 -0
- package/utils/quadHub/createQuadHubGeometry.ts +331 -0
- package/utils/quadHub/index.ts +3 -0
- package/utils/quadHub/resolveQuadHubItems.ts +214 -0
- package/utils/useDiagramAutoScale.ts +300 -0
- package/utils/vertCard/context.ts +18 -0
- package/utils/vertCard/createVertCardGeometry.ts +242 -0
- package/utils/vertCard/index.ts +2 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, Fragment, inject, provide, useSlots, type VNode } from 'vue';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_HEX_TRIAD_ITEMS,
|
|
6
|
+
HEX_TRIAD_CALLOUT_KEY,
|
|
7
|
+
HEX_TRIAD_ROOT_KEY,
|
|
8
|
+
type IHexTriadItem,
|
|
9
|
+
} from '../../utils/hexTriad';
|
|
10
|
+
import HexTriadBadge from './HexTriadBadge.vue';
|
|
11
|
+
import HexTriadContent from './HexTriadContent.vue';
|
|
12
|
+
import HexTriadHeading from './HexTriadHeading.vue';
|
|
13
|
+
|
|
14
|
+
defineOptions({
|
|
15
|
+
name: 'HexTriadCallout',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
interface INamedComponent {
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function hasComponentName(type: unknown, name: string): boolean {
|
|
23
|
+
if (typeof type === 'object' && type !== null && 'name' in type) {
|
|
24
|
+
return (type as INamedComponent).name === name;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rootContext = inject(HEX_TRIAD_ROOT_KEY, undefined);
|
|
31
|
+
|
|
32
|
+
if (!rootContext) {
|
|
33
|
+
console.warn('[HexTriadCallout] Must be used inside <HexTriad>');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const registration = rootContext?.registerCallout();
|
|
37
|
+
const index = registration?.index ?? 0;
|
|
38
|
+
|
|
39
|
+
const fallbackItem = computed<IHexTriadItem>(() => {
|
|
40
|
+
return DEFAULT_HEX_TRIAD_ITEMS[0];
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const item = registration?.item ?? fallbackItem;
|
|
44
|
+
|
|
45
|
+
const setBadgeText = (text: string | number): void => {
|
|
46
|
+
registration?.setBadgeText(text);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
provide(HEX_TRIAD_CALLOUT_KEY, {
|
|
50
|
+
item,
|
|
51
|
+
index,
|
|
52
|
+
setBadgeText,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const slots = useSlots();
|
|
56
|
+
|
|
57
|
+
function flattenVNodes(nodes: VNode[]): VNode[] {
|
|
58
|
+
const result: VNode[] = [];
|
|
59
|
+
|
|
60
|
+
for (const node of nodes) {
|
|
61
|
+
if (node.type === Fragment && Array.isArray(node.children)) {
|
|
62
|
+
result.push(...flattenVNodes(node.children as VNode[]));
|
|
63
|
+
} else {
|
|
64
|
+
result.push(node);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isBadgeNode(node: VNode): boolean {
|
|
72
|
+
return (
|
|
73
|
+
hasComponentName(node.type, 'HexTriadBadge') || node.type === HexTriadBadge
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isHeadingNode(node: VNode): boolean {
|
|
78
|
+
return (
|
|
79
|
+
hasComponentName(node.type, 'HexTriadHeading') ||
|
|
80
|
+
node.type === HexTriadHeading
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isContentNode(node: VNode): boolean {
|
|
85
|
+
return (
|
|
86
|
+
hasComponentName(node.type, 'HexTriadContent') ||
|
|
87
|
+
node.type === HexTriadContent
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const defaultNodes = computed(() => {
|
|
92
|
+
if (!slots.default) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return flattenVNodes(slots.default());
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const badgeNode = computed(() => {
|
|
100
|
+
return defaultNodes.value.find(isBadgeNode);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const headingNode = computed(() => {
|
|
104
|
+
return defaultNodes.value.find(isHeadingNode);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const contentNode = computed(() => {
|
|
108
|
+
return defaultNodes.value.find(isContentNode);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const otherNodes = computed(() => {
|
|
112
|
+
return defaultNodes.value.filter((node) => {
|
|
113
|
+
return !isBadgeNode(node) && !isHeadingNode(node) && !isContentNode(node);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Positions matching the design exactly:
|
|
118
|
+
// Index 0: upper-right pocket above orange hex
|
|
119
|
+
// Index 1: lower-right, right of orange hex
|
|
120
|
+
// Index 2: lower-left, left of blue hex
|
|
121
|
+
const positionStyle = computed(() => {
|
|
122
|
+
if (index === 0) {
|
|
123
|
+
return 'left: calc(50% + 90px); top: 20px;';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (index === 1) {
|
|
127
|
+
return 'left: calc(50% + 185px); top: 210px;';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return 'right: calc(50% + 185px); top: 210px;';
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const animation = computed(() => rootContext?.animation?.value ?? true);
|
|
134
|
+
|
|
135
|
+
const containerClass = computed(() => {
|
|
136
|
+
if (index === 2) {
|
|
137
|
+
return `alpha-hex-triad-callout alpha-hex-triad-callout--${index} absolute flex items-start gap-3 justify-end text-right z-10 pointer-events-auto`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return `alpha-hex-triad-callout alpha-hex-triad-callout--${index} absolute flex items-start gap-3 justify-start text-left z-10 pointer-events-auto`;
|
|
141
|
+
});
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<template>
|
|
145
|
+
<div
|
|
146
|
+
v-if="index < 3"
|
|
147
|
+
:class="[containerClass, { 'alpha-hex-triad-callout--animated': animation }]"
|
|
148
|
+
:style="positionStyle"
|
|
149
|
+
>
|
|
150
|
+
<!-- For Index 0 and 1: Badge on left, Text on right -->
|
|
151
|
+
<template v-if="index !== 2">
|
|
152
|
+
<component :is="badgeNode" v-if="badgeNode" />
|
|
153
|
+
<HexTriadBadge v-else />
|
|
154
|
+
<div class="flex flex-col items-start text-left">
|
|
155
|
+
<component :is="headingNode" v-if="headingNode" />
|
|
156
|
+
<HexTriadHeading v-else />
|
|
157
|
+
<component :is="contentNode" v-if="contentNode" />
|
|
158
|
+
<HexTriadContent v-else />
|
|
159
|
+
<component :is="node" v-for="(node, i) in otherNodes" :key="i" />
|
|
160
|
+
</div>
|
|
161
|
+
</template>
|
|
162
|
+
<!-- For Index 2 (Blue / Left): Text on left, Badge on right -->
|
|
163
|
+
<template v-else>
|
|
164
|
+
<div class="flex flex-col items-end text-right">
|
|
165
|
+
<component :is="headingNode" v-if="headingNode" />
|
|
166
|
+
<HexTriadHeading v-else />
|
|
167
|
+
<component :is="contentNode" v-if="contentNode" />
|
|
168
|
+
<HexTriadContent v-else />
|
|
169
|
+
<component :is="node" v-for="(node, i) in otherNodes" :key="i" />
|
|
170
|
+
</div>
|
|
171
|
+
<component :is="badgeNode" v-if="badgeNode" />
|
|
172
|
+
<HexTriadBadge v-else />
|
|
173
|
+
</template>
|
|
174
|
+
</div>
|
|
175
|
+
</template>
|
|
176
|
+
|
|
177
|
+
<style scoped>
|
|
178
|
+
.alpha-hex-triad-callout--animated.alpha-hex-triad-callout--0 {
|
|
179
|
+
animation: hex-callout-display 400ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--hex-start-delay, 0ms) + 250ms) both;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.alpha-hex-triad-callout--animated.alpha-hex-triad-callout--1 {
|
|
183
|
+
animation: hex-callout-display 400ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--hex-start-delay, 0ms) + 450ms) both;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.alpha-hex-triad-callout--animated.alpha-hex-triad-callout--2 {
|
|
187
|
+
animation: hex-callout-display 400ms cubic-bezier(0.16, 1, 0.3, 1) calc(var(--hex-start-delay, 0ms) + 650ms) both;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
@keyframes hex-callout-display {
|
|
191
|
+
0% {
|
|
192
|
+
opacity: 0;
|
|
193
|
+
transform: translateY(12px);
|
|
194
|
+
}
|
|
195
|
+
100% {
|
|
196
|
+
opacity: 1;
|
|
197
|
+
transform: translateY(0);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
name: 'HexTriadCenterBadge',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export interface IHexTriadCenterBadgeProps {
|
|
9
|
+
readonly x: number;
|
|
10
|
+
readonly y: number;
|
|
11
|
+
readonly text: string | number;
|
|
12
|
+
readonly color: string;
|
|
13
|
+
readonly filter?: string;
|
|
14
|
+
readonly radius: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<IHexTriadCenterBadgeProps>(), {
|
|
18
|
+
filter: 'url(#hex-badge-shadow)',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const transform = computed(() => {
|
|
22
|
+
return `translate(${props.x}, ${props.y})`;
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<g class="alpha-hex-triad-center-badge" :transform="transform" :filter="filter">
|
|
28
|
+
<circle class="alpha-hex-triad-center-badge__outer" cx="0" cy="0" :r="radius + 4" fill="#ffffff" />
|
|
29
|
+
<circle class="alpha-hex-triad-center-badge__circle" cx="0" cy="0" :r="radius" :fill="color" />
|
|
30
|
+
<circle
|
|
31
|
+
class="alpha-hex-triad-center-badge__ring"
|
|
32
|
+
cx="0"
|
|
33
|
+
cy="0"
|
|
34
|
+
:r="radius - 4"
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="#ffffff"
|
|
37
|
+
stroke-width="1.2"
|
|
38
|
+
:style="{ opacity: 0.65 }"
|
|
39
|
+
/>
|
|
40
|
+
<text
|
|
41
|
+
class="alpha-hex-triad-center-badge__text font-black text-lg select-none"
|
|
42
|
+
x="0"
|
|
43
|
+
y="6.5"
|
|
44
|
+
text-anchor="middle"
|
|
45
|
+
fill="#ffffff"
|
|
46
|
+
font-family="system-ui, -apple-system, sans-serif"
|
|
47
|
+
>
|
|
48
|
+
{{ text }}
|
|
49
|
+
</text>
|
|
50
|
+
</g>
|
|
51
|
+
</template>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({
|
|
3
|
+
name: 'HexTriadContent',
|
|
4
|
+
});
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<p
|
|
9
|
+
class="alpha-hex-triad-content mt-0 mb-0 text-[1rem] leading-relaxed max-w-[240px]"
|
|
10
|
+
:style="{
|
|
11
|
+
margin: 0,
|
|
12
|
+
fontSize: '1rem',
|
|
13
|
+
fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
14
|
+
}"
|
|
15
|
+
>
|
|
16
|
+
<slot>
|
|
17
|
+
Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod sed diam nonummy nibh
|
|
18
|
+
</slot>
|
|
19
|
+
</p>
|
|
20
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, inject } from 'vue';
|
|
3
|
+
|
|
4
|
+
import { HEX_TRIAD_CALLOUT_KEY } from '../../utils/hexTriad';
|
|
5
|
+
|
|
6
|
+
defineOptions({
|
|
7
|
+
name: 'HexTriadHeading',
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const calloutContext = inject(HEX_TRIAD_CALLOUT_KEY, undefined);
|
|
11
|
+
const headingColor = computed(() => {
|
|
12
|
+
return calloutContext?.item.value.color ?? '#e11d48';
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<h3
|
|
18
|
+
class="alpha-hex-triad-heading font-bold text-[1.25rem] tracking-tight mb-[0.5rem]"
|
|
19
|
+
:style="{
|
|
20
|
+
color: headingColor,
|
|
21
|
+
fontSize: '1.25rem',
|
|
22
|
+
fontFamily:
|
|
23
|
+
'system-ui, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
24
|
+
}"
|
|
25
|
+
>
|
|
26
|
+
<slot>Text Here</slot>
|
|
27
|
+
</h3>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({
|
|
3
|
+
name: 'HexTriadLayer',
|
|
4
|
+
});
|
|
5
|
+
|
|
6
|
+
export interface IHexTriadLayerProps {
|
|
7
|
+
readonly d: string;
|
|
8
|
+
readonly fill?: string;
|
|
9
|
+
readonly filter?: string;
|
|
10
|
+
readonly opacity?: number | string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
withDefaults(defineProps<IHexTriadLayerProps>(), {
|
|
14
|
+
opacity: 1,
|
|
15
|
+
fill: undefined,
|
|
16
|
+
filter: undefined,
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<path
|
|
22
|
+
class="alpha-hex-triad-layer"
|
|
23
|
+
:d="d"
|
|
24
|
+
:fill="fill"
|
|
25
|
+
:filter="filter"
|
|
26
|
+
:style="opacity !== undefined ? { opacity } : undefined"
|
|
27
|
+
/>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,318 @@
|
|
|
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 {
|
|
12
|
+
createHorizCardAccentPath,
|
|
13
|
+
createHorizCardPath,
|
|
14
|
+
HORIZ_CARD_KEY,
|
|
15
|
+
hexToRgba,
|
|
16
|
+
} from '../../utils/horizCard';
|
|
17
|
+
import { useMergedUnoAttrs } from '../../utils/useMergedUnoAttrs';
|
|
18
|
+
|
|
19
|
+
defineOptions({
|
|
20
|
+
name: 'HorizCard',
|
|
21
|
+
inheritAttrs: false,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export interface IHorizCardProps {
|
|
25
|
+
readonly color?: string;
|
|
26
|
+
readonly dots?: boolean;
|
|
27
|
+
readonly title?: string;
|
|
28
|
+
readonly cardBg?: string;
|
|
29
|
+
readonly textColor?: string;
|
|
30
|
+
readonly titleColor?: string;
|
|
31
|
+
readonly lightColor?: string;
|
|
32
|
+
readonly description?: string;
|
|
33
|
+
readonly step?: string | number;
|
|
34
|
+
readonly icon?: string | object;
|
|
35
|
+
readonly hasIconCircle?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const props = withDefaults(defineProps<IHorizCardProps>(), {
|
|
39
|
+
dots: true,
|
|
40
|
+
step: '01',
|
|
41
|
+
icon: undefined,
|
|
42
|
+
color: '#ea583a',
|
|
43
|
+
cardBg: '#ffffff',
|
|
44
|
+
title: 'Lorem Ipsum',
|
|
45
|
+
textColor: undefined,
|
|
46
|
+
lightColor: undefined,
|
|
47
|
+
titleColor: undefined,
|
|
48
|
+
description: undefined,
|
|
49
|
+
hasIconCircle: undefined,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const slots = useSlots();
|
|
53
|
+
|
|
54
|
+
interface INamedComponent {
|
|
55
|
+
readonly name?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasComponentName(type: unknown, name: string): boolean {
|
|
59
|
+
if (typeof type === 'object' && type !== null && 'name' in type) {
|
|
60
|
+
return (type as INamedComponent).name === name;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function flattenVNodes(nodes: VNode[]): VNode[] {
|
|
67
|
+
const result: VNode[] = [];
|
|
68
|
+
|
|
69
|
+
for (const node of nodes) {
|
|
70
|
+
if (node.type === Fragment && Array.isArray(node.children)) {
|
|
71
|
+
result.push(...flattenVNodes(node.children as VNode[]));
|
|
72
|
+
} else {
|
|
73
|
+
result.push(node);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isBadgeNode(node: VNode): boolean {
|
|
81
|
+
return hasComponentName(node.type, 'HorizCardBadge');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isIconNode(node: VNode): boolean {
|
|
85
|
+
return hasComponentName(node.type, 'HorizCardIcon');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isHeadingNode(node: VNode): boolean {
|
|
89
|
+
return hasComponentName(node.type, 'HorizCardHeading');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isContentNode(node: VNode): boolean {
|
|
93
|
+
return hasComponentName(node.type, 'HorizCardContent');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const defaultNodes = computed(() => {
|
|
97
|
+
if (!slots.default) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return flattenVNodes(slots.default());
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const slottedBadge = computed(() => {
|
|
105
|
+
return defaultNodes.value.find(isBadgeNode);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const slottedIcon = computed(() => {
|
|
109
|
+
return defaultNodes.value.find(isIconNode);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const slottedHeading = computed(() => {
|
|
113
|
+
return defaultNodes.value.find(isHeadingNode);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const slottedContent = computed(() => {
|
|
117
|
+
return defaultNodes.value.find(isContentNode);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const otherSlottedNodes = computed(() => {
|
|
121
|
+
return defaultNodes.value.filter((node) => {
|
|
122
|
+
return (
|
|
123
|
+
!isBadgeNode(node) &&
|
|
124
|
+
!isIconNode(node) &&
|
|
125
|
+
!isHeadingNode(node) &&
|
|
126
|
+
!isContentNode(node)
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const resolvedColor = computed(() => {
|
|
132
|
+
return props.color;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const resolvedLightColor = computed(() => {
|
|
136
|
+
return (
|
|
137
|
+
props.lightColor ??
|
|
138
|
+
hexToRgba({
|
|
139
|
+
alpha: 0.16,
|
|
140
|
+
hex: props.color,
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const resolvedCardBg = computed(() => {
|
|
146
|
+
return props.cardBg;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const resolvedTitleColor = computed(() => {
|
|
150
|
+
return props.titleColor ?? props.color;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const resolvedTextColor = computed(() => {
|
|
154
|
+
return props.textColor;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const resolvedStep = computed(() => {
|
|
158
|
+
return props.step;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
provide(HORIZ_CARD_KEY, {
|
|
162
|
+
step: resolvedStep,
|
|
163
|
+
color: resolvedColor,
|
|
164
|
+
cardBg: resolvedCardBg,
|
|
165
|
+
lightColor: resolvedLightColor,
|
|
166
|
+
titleColor: resolvedTitleColor,
|
|
167
|
+
textColor: resolvedTextColor as ComputedRef<string | undefined>,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Fixed coordinate grid for crisp SVG vector drawing
|
|
171
|
+
const CARD_WIDTH = 360;
|
|
172
|
+
const CARD_HEIGHT = 260;
|
|
173
|
+
|
|
174
|
+
const cardPath = computed(() => {
|
|
175
|
+
return createHorizCardPath({
|
|
176
|
+
chamferX: 60,
|
|
177
|
+
chamferY: 76,
|
|
178
|
+
borderRadius: 24,
|
|
179
|
+
chamferFillet: 22,
|
|
180
|
+
width: CARD_WIDTH,
|
|
181
|
+
height: CARD_HEIGHT,
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const accentPath = computed(() => {
|
|
186
|
+
return createHorizCardAccentPath({
|
|
187
|
+
chamferX: 60,
|
|
188
|
+
chamferY: 76,
|
|
189
|
+
chamferFillet: 22,
|
|
190
|
+
width: CARD_WIDTH,
|
|
191
|
+
height: CARD_HEIGHT,
|
|
192
|
+
accentThickness: 16,
|
|
193
|
+
accentTopFillet: 22,
|
|
194
|
+
accentBottomFillet: 22,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Determine whether icon circle is active
|
|
199
|
+
const isIconActive = computed(() => {
|
|
200
|
+
if (props.hasIconCircle !== undefined) {
|
|
201
|
+
return props.hasIconCircle;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return Boolean(props.icon || slots.icon || slottedIcon.value !== undefined);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const { className, forwardedAttrs } = useMergedUnoAttrs(
|
|
208
|
+
'alpha-horiz-card relative inline-block select-none w-[360px] h-[260px] font-sans',
|
|
209
|
+
);
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<template>
|
|
213
|
+
<div v-bind="forwardedAttrs()" :class="className()">
|
|
214
|
+
<!-- SVG Vector Layer: Bottom-left accent blob + filleted chamfered card outline -->
|
|
215
|
+
<svg
|
|
216
|
+
class="absolute -left-[28px] -top-[16px] w-[416px] h-[292px] pointer-events-none overflow-visible"
|
|
217
|
+
viewBox="-28 -16 416 292"
|
|
218
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
219
|
+
>
|
|
220
|
+
<!-- Layer 1: Solid accent shape behind the card & chamfer -->
|
|
221
|
+
<path
|
|
222
|
+
class="alpha-horiz-card__accent"
|
|
223
|
+
:d="accentPath"
|
|
224
|
+
:fill="resolvedColor"
|
|
225
|
+
/>
|
|
226
|
+
<!-- Layer 2: Main card body with stroke border and filleted chamfered cut -->
|
|
227
|
+
<path
|
|
228
|
+
class="alpha-horiz-card__body"
|
|
229
|
+
:d="cardPath"
|
|
230
|
+
:fill="resolvedCardBg"
|
|
231
|
+
:stroke="resolvedColor"
|
|
232
|
+
stroke-width="2.75"
|
|
233
|
+
stroke-linejoin="round"
|
|
234
|
+
/>
|
|
235
|
+
</svg>
|
|
236
|
+
<!-- Ribbon Step Badge at top left (hangs over the top border) -->
|
|
237
|
+
<div class="absolute left-[20px] -top-[20px] z-10">
|
|
238
|
+
<slot name="badge">
|
|
239
|
+
<component :is="slottedBadge" v-if="slottedBadge" />
|
|
240
|
+
<HorizCardBadge v-else :step="resolvedStep" :color="resolvedColor" />
|
|
241
|
+
</slot>
|
|
242
|
+
</div>
|
|
243
|
+
<!-- Card Content Layer -->
|
|
244
|
+
<div
|
|
245
|
+
class="relative z-10 w-full h-full pt-10 pb-6 px-6 flex flex-col justify-between"
|
|
246
|
+
>
|
|
247
|
+
<!-- Middle Row: Optional Icon Circle + Text Content -->
|
|
248
|
+
<div
|
|
249
|
+
class="flex items-center gap-4 flex-1"
|
|
250
|
+
:class="{ 'pl-1': isIconActive }"
|
|
251
|
+
>
|
|
252
|
+
<!-- Icon Circle (rendered when icon/slot is provided) -->
|
|
253
|
+
<template v-if="isIconActive">
|
|
254
|
+
<slot name="icon">
|
|
255
|
+
<component :is="slottedIcon" v-if="slottedIcon" />
|
|
256
|
+
<HorizCardIcon
|
|
257
|
+
v-else
|
|
258
|
+
:icon="props.icon"
|
|
259
|
+
:color="resolvedColor"
|
|
260
|
+
:light-color="resolvedLightColor"
|
|
261
|
+
:size="100"
|
|
262
|
+
/>
|
|
263
|
+
</slot>
|
|
264
|
+
</template>
|
|
265
|
+
<!-- Heading + Body Description -->
|
|
266
|
+
<div class="flex-1 flex flex-col justify-center min-w-0 pr-1">
|
|
267
|
+
<slot name="title">
|
|
268
|
+
<component :is="slottedHeading" v-if="slottedHeading" />
|
|
269
|
+
<HorizCardHeading
|
|
270
|
+
v-else
|
|
271
|
+
:color="resolvedTitleColor"
|
|
272
|
+
class="mb-2 text-lg"
|
|
273
|
+
>
|
|
274
|
+
{{ props.title }}
|
|
275
|
+
</HorizCardHeading>
|
|
276
|
+
</slot>
|
|
277
|
+
<slot name="description">
|
|
278
|
+
<component :is="slottedContent" v-if="slottedContent" />
|
|
279
|
+
<HorizCardContent
|
|
280
|
+
v-else
|
|
281
|
+
:color="resolvedTextColor"
|
|
282
|
+
class="text-[11px] leading-[1.45]"
|
|
283
|
+
>
|
|
284
|
+
<template v-if="props.description">
|
|
285
|
+
{{ props.description }}
|
|
286
|
+
</template>
|
|
287
|
+
</HorizCardContent>
|
|
288
|
+
</slot>
|
|
289
|
+
<!-- Any other custom slotted components -->
|
|
290
|
+
<component
|
|
291
|
+
:is="node"
|
|
292
|
+
v-for="(node, index) in otherSlottedNodes"
|
|
293
|
+
:key="index"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
<!-- Bottom Row: Decorative Indicator Dots -->
|
|
298
|
+
<div v-if="props.dots" class="flex justify-end items-center pr-2 pb-1">
|
|
299
|
+
<slot name="dots">
|
|
300
|
+
<div class="inline-flex items-center gap-1.5 opacity-90">
|
|
301
|
+
<span
|
|
302
|
+
class="w-2 h-2 rounded-full inline-block"
|
|
303
|
+
:style="{ backgroundColor: resolvedColor }"
|
|
304
|
+
/>
|
|
305
|
+
<span
|
|
306
|
+
class="w-2 h-2 rounded-full inline-block"
|
|
307
|
+
:style="{ backgroundColor: resolvedColor }"
|
|
308
|
+
/>
|
|
309
|
+
<span
|
|
310
|
+
class="w-2 h-2 rounded-full inline-block"
|
|
311
|
+
:style="{ backgroundColor: resolvedColor }"
|
|
312
|
+
/>
|
|
313
|
+
</div>
|
|
314
|
+
</slot>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</template>
|