@eox/pages-theme-eox 2.0.0-alpha.1 → 2.0.0-alpha.10
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/.gitlab-ci.yml +4 -1
- package/README.md +75 -11
- package/cypress/support/mocks/authors.data.js +14 -0
- package/cypress/support/mocks/helpers.js +65 -4
- package/cypress.config.js +9 -0
- package/package.json +3 -1
- package/src/Layout.vue +14 -3
- package/src/components/BlogList.vue +74 -55
- package/src/components/BlogPost.vue +46 -24
- package/src/components/CTASection.vue +18 -4
- package/src/components/FeatureCard/Index.vue +113 -26
- package/src/components/FeatureCard/Poster.vue +25 -3
- package/src/components/FeatureSection.cy.js +16 -0
- package/src/components/FeatureSection.vue +3 -8
- package/src/components/FeaturesGallery.vue +34 -2
- package/src/components/Footer.cy.js +52 -2
- package/src/components/Footer.vue +17 -5
- package/src/components/HeroSection.cy.js +20 -0
- package/src/components/HeroSection.vue +139 -24
- package/src/components/ImageCarousel.vue +11 -2
- package/src/components/ImageElement.vue +16 -4
- package/src/components/ImageGallery.vue +29 -4
- package/src/components/LogoSection.vue +26 -3
- package/src/components/MobileNavDropdown.vue +13 -3
- package/src/components/NavBar.cy.js +15 -0
- package/src/components/NavBar.vue +149 -21
- package/src/components/NavDropdown.vue +12 -2
- package/src/components/OpenPositions.vue +292 -0
- package/src/components/TagList.vue +32 -14
- package/src/components/TeamPage.vue +34 -20
- package/src/components/TestimonialsSection.cy.js +2 -5
- package/src/components/TestimonialsSection.vue +119 -16
- package/src/components/TextSection.cy.js +115 -0
- package/src/components/TextSection.vue +396 -0
- package/src/helpers.js +182 -37
- package/src/helpers.test.js +181 -0
- package/src/index.js +4 -0
- package/src/pages/author/[author].md +1 -0
- package/src/pages/author/[author].paths.js +2 -2
- package/src/pages/blog.md +1 -0
- package/src/pages/tag/[tag].md +1 -0
- package/src/pages/tag.md +1 -0
- package/src/pages/team.md +2 -1
- package/src/posts.data.js +39 -1
- package/src/style.css +232 -11
- package/src/tagUtils.js +5 -5
- package/src/vitepressConfig.mjs +6 -3
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
{{ formatDate(frontmatter.date) }}
|
|
31
31
|
</time>
|
|
32
32
|
</span>
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
<!-- Author Span -->
|
|
35
35
|
<span v-if="authors && authors.length" class="metadata-item">
|
|
36
36
|
<i class="mdi mdi-account-outline metadata-icon"></i>
|
|
@@ -41,12 +41,14 @@
|
|
|
41
41
|
class="author-item"
|
|
42
42
|
>
|
|
43
43
|
<a
|
|
44
|
-
:href="`/author/${authorId}/`"
|
|
44
|
+
:href="withBase(`/author/${authorId}/`)"
|
|
45
45
|
class="author-link-active"
|
|
46
46
|
>
|
|
47
47
|
{{ getAuthorName(authorId) }}
|
|
48
48
|
</a>
|
|
49
|
-
<span v-if="idx < authors.length - 1" class="author-separator"
|
|
49
|
+
<span v-if="idx < authors.length - 1" class="author-separator"
|
|
50
|
+
>,</span
|
|
51
|
+
>
|
|
50
52
|
</span>
|
|
51
53
|
</span>
|
|
52
54
|
</div>
|
|
@@ -59,7 +61,7 @@
|
|
|
59
61
|
<a
|
|
60
62
|
v-for="tag in frontmatter.tags"
|
|
61
63
|
:key="tag"
|
|
62
|
-
:href="`/tag/${tag}/`"
|
|
64
|
+
:href="withBase(`/tag/${tag}/`)"
|
|
63
65
|
:style="getTagStyle(tag)"
|
|
64
66
|
class="post-tag"
|
|
65
67
|
>
|
|
@@ -77,23 +79,18 @@
|
|
|
77
79
|
<!-- Sidebar Column -->
|
|
78
80
|
<div class="blog-sidebar-col">
|
|
79
81
|
<!-- Table of Contents (On This Page) -->
|
|
80
|
-
<div
|
|
81
|
-
v-if="headers && headers.length"
|
|
82
|
-
class="toc-container"
|
|
83
|
-
>
|
|
82
|
+
<div v-if="headers && headers.length" class="toc-container">
|
|
84
83
|
<VPDocAsideOutline />
|
|
85
84
|
</div>
|
|
86
85
|
|
|
87
86
|
<!-- Hot Tags -->
|
|
88
87
|
<div class="sidebar-widget">
|
|
89
|
-
<h3 class="sidebar-title">
|
|
90
|
-
Hot tags
|
|
91
|
-
</h3>
|
|
88
|
+
<h3 class="sidebar-title">Hot tags</h3>
|
|
92
89
|
<div class="sidebar-tags-container">
|
|
93
90
|
<a
|
|
94
91
|
v-for="tag in calculatedHotTags"
|
|
95
92
|
:key="tag.name"
|
|
96
|
-
:href="`/tag/${tag.name}/`"
|
|
93
|
+
:href="withBase(`/tag/${tag.name}/`)"
|
|
97
94
|
:style="getTagStyle(tag.name)"
|
|
98
95
|
class="sidebar-tag"
|
|
99
96
|
>
|
|
@@ -104,9 +101,7 @@
|
|
|
104
101
|
|
|
105
102
|
<!-- Recent Posts -->
|
|
106
103
|
<div class="sidebar-widget">
|
|
107
|
-
<h3 class="sidebar-title">
|
|
108
|
-
Recent posts
|
|
109
|
-
</h3>
|
|
104
|
+
<h3 class="sidebar-title">Recent posts</h3>
|
|
110
105
|
<div class="space-y-4">
|
|
111
106
|
<div
|
|
112
107
|
v-for="post in calculatedRecentPosts"
|
|
@@ -114,7 +109,7 @@
|
|
|
114
109
|
class="flex flex-col gap-1 border-b border-slate-100 last:border-none pb-3 last:pb-0"
|
|
115
110
|
>
|
|
116
111
|
<a
|
|
117
|
-
:href="post.url"
|
|
112
|
+
:href="withBase(post.url)"
|
|
118
113
|
class="text-sm font-semibold text-slate-900 hover:text-primary leading-snug transition-colors line-clamp-2"
|
|
119
114
|
>
|
|
120
115
|
{{ post.title }}
|
|
@@ -131,7 +126,7 @@
|
|
|
131
126
|
</template>
|
|
132
127
|
|
|
133
128
|
<script setup>
|
|
134
|
-
import { useData } from "vitepress";
|
|
129
|
+
import { useData, withBase } from "vitepress";
|
|
135
130
|
import { computed } from "vue";
|
|
136
131
|
import { data as posts } from "../posts.data.js";
|
|
137
132
|
import { data as authorsList } from "../authors.data.js";
|
|
@@ -153,10 +148,17 @@ const headers = computed(() => {
|
|
|
153
148
|
// Form cover image URL correctly (prefixing filename with /images/)
|
|
154
149
|
function getCoverImageUrl(coverImage) {
|
|
155
150
|
if (!coverImage) return "";
|
|
156
|
-
if (coverImage.startsWith("
|
|
151
|
+
if (coverImage.startsWith("http") || coverImage.startsWith("//")) {
|
|
152
|
+
return coverImage;
|
|
153
|
+
}
|
|
154
|
+
const base = withBase("/");
|
|
155
|
+
if (base && base !== "/" && coverImage.startsWith(base)) {
|
|
157
156
|
return coverImage;
|
|
158
157
|
}
|
|
159
|
-
|
|
158
|
+
const path = coverImage.startsWith("/")
|
|
159
|
+
? coverImage
|
|
160
|
+
: `/images/${coverImage}`;
|
|
161
|
+
return withBase(path);
|
|
160
162
|
}
|
|
161
163
|
|
|
162
164
|
// Format publication date
|
|
@@ -189,7 +191,19 @@ const coverImage = computed(() => {
|
|
|
189
191
|
|
|
190
192
|
// Dynamic Recent Posts (excluding current page)
|
|
191
193
|
const calculatedRecentPosts = computed(() => {
|
|
192
|
-
|
|
194
|
+
const rel = page.value.relativePath || "";
|
|
195
|
+
const current = (
|
|
196
|
+
"/" + rel.replace(/(^|\/)index\.md$/, "$1").replace(/\.md$/, "")
|
|
197
|
+
)
|
|
198
|
+
.replace(/\/$/, "")
|
|
199
|
+
.replace(/\.html$/, "");
|
|
200
|
+
|
|
201
|
+
return posts
|
|
202
|
+
.filter((p) => {
|
|
203
|
+
const pUrl = (p.url || "").replace(/\/$/, "").replace(/\.html$/, "");
|
|
204
|
+
return pUrl !== current && p.title !== frontmatter.value.title;
|
|
205
|
+
})
|
|
206
|
+
.slice(0, 5);
|
|
193
207
|
});
|
|
194
208
|
|
|
195
209
|
// Dynamic Hot Tags
|
|
@@ -320,7 +334,9 @@ const calculatedHotTags = computed(() => {
|
|
|
320
334
|
align-items: center;
|
|
321
335
|
justify-content: center;
|
|
322
336
|
height: 100%;
|
|
323
|
-
transform: translateY(
|
|
337
|
+
transform: translateY(
|
|
338
|
+
0.5px
|
|
339
|
+
); /* Micro-adjustment for perfect vertical alignment */
|
|
324
340
|
}
|
|
325
341
|
|
|
326
342
|
.metadata-text {
|
|
@@ -343,7 +359,9 @@ const calculatedHotTags = computed(() => {
|
|
|
343
359
|
font-weight: 600;
|
|
344
360
|
color: var(--vp-c-brand, #004170) !important;
|
|
345
361
|
text-decoration: none;
|
|
346
|
-
transition:
|
|
362
|
+
transition:
|
|
363
|
+
color 0.15s ease,
|
|
364
|
+
text-decoration 0.15s ease;
|
|
347
365
|
line-height: 1.2;
|
|
348
366
|
}
|
|
349
367
|
|
|
@@ -391,7 +409,9 @@ const calculatedHotTags = computed(() => {
|
|
|
391
409
|
border-radius: 4px !important;
|
|
392
410
|
text-decoration: none !important;
|
|
393
411
|
line-height: 1.2 !important;
|
|
394
|
-
transition:
|
|
412
|
+
transition:
|
|
413
|
+
opacity 0.2s ease,
|
|
414
|
+
transform 0.1s ease !important;
|
|
395
415
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
|
396
416
|
}
|
|
397
417
|
|
|
@@ -416,7 +436,9 @@ const calculatedHotTags = computed(() => {
|
|
|
416
436
|
border-radius: 4px !important;
|
|
417
437
|
text-decoration: none !important;
|
|
418
438
|
line-height: 1.2 !important;
|
|
419
|
-
transition:
|
|
439
|
+
transition:
|
|
440
|
+
opacity 0.2s ease,
|
|
441
|
+
transform 0.1s ease !important;
|
|
420
442
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important;
|
|
421
443
|
}
|
|
422
444
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
:class="
|
|
5
5
|
dark !== undefined
|
|
6
6
|
? 'primary primary-gradient-bg'
|
|
7
|
-
: 'surface-container-
|
|
7
|
+
: 'surface-container-lowest'
|
|
8
8
|
"
|
|
9
9
|
>
|
|
10
10
|
<div class="large-space"></div>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
v-if="primaryButton"
|
|
19
19
|
class="button extra small-margin responsive-mobile"
|
|
20
20
|
:class="dark !== undefined ? 'surface' : 'primary'"
|
|
21
|
-
:href="
|
|
21
|
+
:href="resolvedPrimaryLink"
|
|
22
22
|
@click="
|
|
23
23
|
trackEvent(['CTA', 'Click', 'CTA Section Primary', primaryButton])
|
|
24
24
|
"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
<a
|
|
28
28
|
v-if="secondaryButton"
|
|
29
29
|
class="button extra small-margin secondary responsive-mobile"
|
|
30
|
-
:href="
|
|
30
|
+
:href="resolvedSecondaryLink"
|
|
31
31
|
@click="
|
|
32
32
|
trackEvent(['CTA', 'Click', 'CTA Section Secondary', secondaryButton])
|
|
33
33
|
"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
v-if="altButton"
|
|
38
38
|
class="button border extra small-margin responsive-mobile"
|
|
39
39
|
:class="dark !== undefined ? 'white-text' : ''"
|
|
40
|
-
:href="
|
|
40
|
+
:href="resolvedAltLink"
|
|
41
41
|
@click="trackEvent(['CTA', 'Click', 'CTA Section Alt', altButton])"
|
|
42
42
|
>{{ altButton }}</a
|
|
43
43
|
>
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
</template>
|
|
50
50
|
|
|
51
51
|
<script setup>
|
|
52
|
+
import { computed } from "vue";
|
|
53
|
+
import { withBase } from "vitepress";
|
|
52
54
|
import { trackEvent } from "../helpers";
|
|
53
55
|
const props = defineProps([
|
|
54
56
|
"dark",
|
|
@@ -61,6 +63,18 @@ const props = defineProps([
|
|
|
61
63
|
"tagline",
|
|
62
64
|
"title",
|
|
63
65
|
]);
|
|
66
|
+
|
|
67
|
+
function resolveLink(link) {
|
|
68
|
+
if (!link) return "";
|
|
69
|
+
if (link.startsWith("http") || link.startsWith("//")) return link;
|
|
70
|
+
const base = withBase("/");
|
|
71
|
+
if (base && base !== "/" && link.startsWith(base)) return link;
|
|
72
|
+
return link.startsWith("/") ? withBase(link) : withBase("/" + link);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const resolvedPrimaryLink = computed(() => resolveLink(props.primaryLink));
|
|
76
|
+
const resolvedSecondaryLink = computed(() => resolveLink(props.secondaryLink));
|
|
77
|
+
const resolvedAltLink = computed(() => resolveLink(props.altLink));
|
|
64
78
|
</script>
|
|
65
79
|
|
|
66
80
|
<style>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { withBase } from "vitepress";
|
|
3
4
|
import { data as authorsList } from "../../authors.data.js";
|
|
4
5
|
import { getTagColor } from "../../tagUtils.js";
|
|
5
6
|
|
|
@@ -33,8 +34,8 @@ const props = defineProps({
|
|
|
33
34
|
required: false,
|
|
34
35
|
},
|
|
35
36
|
icon: {
|
|
36
|
-
/** @type {import('vue').PropType<{ html: string, width?: number, height?: number }>} */
|
|
37
|
-
type: Object,
|
|
37
|
+
/** @type {import('vue').PropType<string | { html: string, width?: number, height?: number }>} */
|
|
38
|
+
type: [Object, String],
|
|
38
39
|
required: false,
|
|
39
40
|
},
|
|
40
41
|
date: {
|
|
@@ -53,6 +54,22 @@ const props = defineProps({
|
|
|
53
54
|
},
|
|
54
55
|
});
|
|
55
56
|
|
|
57
|
+
function resolveUrl(url) {
|
|
58
|
+
if (!url) return "";
|
|
59
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
60
|
+
const base = withBase("/");
|
|
61
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
62
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
66
|
+
const resolvedLinkHref = computed(() =>
|
|
67
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
68
|
+
);
|
|
69
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
70
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
71
|
+
);
|
|
72
|
+
|
|
56
73
|
const iconStyle = {
|
|
57
74
|
width: (props.icon?.width ?? 40) + "px",
|
|
58
75
|
height: (props.icon?.height ?? 40) + "px",
|
|
@@ -60,7 +77,10 @@ const iconStyle = {
|
|
|
60
77
|
|
|
61
78
|
// Check if we should render a full-width cover image
|
|
62
79
|
const isCoverLayout = computed(() => {
|
|
63
|
-
return
|
|
80
|
+
return (
|
|
81
|
+
props.image &&
|
|
82
|
+
(props.variant === "cover" || props.date || props.authors.length)
|
|
83
|
+
);
|
|
64
84
|
});
|
|
65
85
|
|
|
66
86
|
// Check if the card itself is fully clickable (has a link but NO link text)
|
|
@@ -71,7 +91,7 @@ const isClickableCard = computed(() => {
|
|
|
71
91
|
function getAuthorName(authorId) {
|
|
72
92
|
if (!authorId) return "EOX Experts";
|
|
73
93
|
const author = authorsList.find(
|
|
74
|
-
(a) => a.id.toLowerCase().trim() === authorId.toLowerCase().trim()
|
|
94
|
+
(a) => a.id.toLowerCase().trim() === authorId.toLowerCase().trim(),
|
|
75
95
|
);
|
|
76
96
|
return author ? author.name : authorId;
|
|
77
97
|
}
|
|
@@ -82,31 +102,31 @@ function getTagStyle(tag) {
|
|
|
82
102
|
return {
|
|
83
103
|
backgroundColor: "rgba(255, 94, 94, 0.15)",
|
|
84
104
|
border: "1px solid rgb(255, 94, 94)",
|
|
85
|
-
color: "#d32f2f"
|
|
105
|
+
color: "#d32f2f",
|
|
86
106
|
};
|
|
87
107
|
} else if (color.startsWith("rgb(0, 177, 176)")) {
|
|
88
108
|
return {
|
|
89
109
|
backgroundColor: "rgba(0, 177, 176, 0.15)",
|
|
90
110
|
border: "1px solid rgb(0, 177, 176)",
|
|
91
|
-
color: "#00796b"
|
|
111
|
+
color: "#00796b",
|
|
92
112
|
};
|
|
93
113
|
} else if (color.startsWith("rgb(219, 173, 66)")) {
|
|
94
114
|
return {
|
|
95
115
|
backgroundColor: "rgba(219, 173, 66, 0.15)",
|
|
96
116
|
border: "1px solid rgb(219, 173, 66)",
|
|
97
|
-
color: "#f57c00"
|
|
117
|
+
color: "#f57c00",
|
|
98
118
|
};
|
|
99
119
|
} else if (color.startsWith("rgb(218, 71, 228)")) {
|
|
100
120
|
return {
|
|
101
121
|
backgroundColor: "rgba(218, 71, 228, 0.15)",
|
|
102
122
|
border: "1px solid rgb(218, 71, 228)",
|
|
103
|
-
color: "#c2185b"
|
|
123
|
+
color: "#c2185b",
|
|
104
124
|
};
|
|
105
125
|
}
|
|
106
126
|
return {
|
|
107
127
|
backgroundColor: "rgba(0, 0, 0, 0.05)",
|
|
108
128
|
border: "1px solid rgba(0, 0, 0, 0.1)",
|
|
109
|
-
color: "#475569"
|
|
129
|
+
color: "#475569",
|
|
110
130
|
};
|
|
111
131
|
}
|
|
112
132
|
|
|
@@ -126,27 +146,64 @@ function formatDate(dateString) {
|
|
|
126
146
|
class="vertical large-padding no-margin theme-feature-card"
|
|
127
147
|
:class="[
|
|
128
148
|
isClickableCard ? 'small-elevate clickable-card' : 'no-elevate',
|
|
129
|
-
isCoverLayout ? 'has-cover-image' : ''
|
|
149
|
+
isCoverLayout ? 'has-cover-image' : '',
|
|
130
150
|
]"
|
|
131
151
|
>
|
|
132
152
|
<!-- Cover Image for cover/blog/rich cards -->
|
|
133
|
-
<a
|
|
134
|
-
|
|
153
|
+
<a
|
|
154
|
+
v-if="isCoverLayout && resolvedLinkHref"
|
|
155
|
+
:href="resolvedLinkHref"
|
|
156
|
+
:target="link?.target ?? '_blank'"
|
|
157
|
+
class="card-cover-link"
|
|
158
|
+
>
|
|
159
|
+
<img
|
|
160
|
+
:src="resolvedImage"
|
|
161
|
+
alt=""
|
|
162
|
+
class="card-cover-image"
|
|
163
|
+
loading="lazy"
|
|
164
|
+
/>
|
|
135
165
|
</a>
|
|
166
|
+
<div v-else-if="isCoverLayout" class="card-cover-link">
|
|
167
|
+
<img
|
|
168
|
+
:src="resolvedImage"
|
|
169
|
+
alt=""
|
|
170
|
+
class="card-cover-image"
|
|
171
|
+
loading="lazy"
|
|
172
|
+
/>
|
|
173
|
+
</div>
|
|
136
174
|
|
|
137
175
|
<div class="card-content-wrapper">
|
|
138
176
|
<i
|
|
139
177
|
v-if="typeof icon === 'string' && icon.startsWith('mdi-')"
|
|
140
178
|
:class="`mdi ${icon}`"
|
|
141
179
|
></i>
|
|
180
|
+
<img
|
|
181
|
+
v-else-if="typeof icon === 'string'"
|
|
182
|
+
:src="resolveUrl(icon)"
|
|
183
|
+
alt=""
|
|
184
|
+
class="icon"
|
|
185
|
+
style="
|
|
186
|
+
height: 36px;
|
|
187
|
+
width: auto;
|
|
188
|
+
max-width: 120px;
|
|
189
|
+
object-fit: contain;
|
|
190
|
+
margin-bottom: 0.75rem;
|
|
191
|
+
border-radius: 0 !important;
|
|
192
|
+
"
|
|
193
|
+
/>
|
|
142
194
|
<div
|
|
143
|
-
v-else-if="icon"
|
|
195
|
+
v-else-if="icon && icon.html"
|
|
144
196
|
:style="iconStyle"
|
|
145
197
|
v-html="icon.html"
|
|
146
198
|
class="icon"
|
|
147
199
|
></div>
|
|
148
200
|
<!-- Render small icon image only if NOT rendering a full cover image -->
|
|
149
|
-
<img
|
|
201
|
+
<img
|
|
202
|
+
v-else-if="image && !isCoverLayout"
|
|
203
|
+
:src="resolvedImage"
|
|
204
|
+
alt=""
|
|
205
|
+
class="icon"
|
|
206
|
+
/>
|
|
150
207
|
|
|
151
208
|
<!-- Metadata & Tags directly under place/author -->
|
|
152
209
|
<div v-if="date || (authors && authors.length)" class="metadata-wrapper">
|
|
@@ -159,18 +216,33 @@ function formatDate(dateString) {
|
|
|
159
216
|
<span class="author-icon">
|
|
160
217
|
<i class="mdi mdi-account-outline"></i>
|
|
161
218
|
</span>
|
|
162
|
-
<span
|
|
163
|
-
|
|
219
|
+
<span
|
|
220
|
+
v-for="(authorId, idx) in authors"
|
|
221
|
+
:key="authorId"
|
|
222
|
+
class="author-item"
|
|
223
|
+
>
|
|
224
|
+
<a :href="withBase(`/author/${authorId}/`)" class="author-link">{{
|
|
225
|
+
getAuthorName(authorId)
|
|
226
|
+
}}</a
|
|
227
|
+
><span v-if="idx < authors.length - 1" class="author-separator"
|
|
228
|
+
>,</span
|
|
229
|
+
>
|
|
164
230
|
</span>
|
|
165
231
|
</span>
|
|
166
232
|
</div>
|
|
167
233
|
|
|
168
234
|
<!-- Tags directly under metadata if chips are provided and this is a rich card -->
|
|
169
235
|
<div v-if="chips && chips.length" class="blog-tags-container">
|
|
170
|
-
<a
|
|
171
|
-
v-for="chip in chips"
|
|
236
|
+
<a
|
|
237
|
+
v-for="chip in chips"
|
|
172
238
|
:key="chip.text"
|
|
173
|
-
:href="
|
|
239
|
+
:href="
|
|
240
|
+
chip.link
|
|
241
|
+
? resolveUrl(chip.link)
|
|
242
|
+
: chip.href
|
|
243
|
+
? resolveUrl(chip.href)
|
|
244
|
+
: withBase(`/tag/${chip.text}/`)
|
|
245
|
+
"
|
|
174
246
|
:style="getTagStyle(chip.text)"
|
|
175
247
|
class="tag-link"
|
|
176
248
|
>
|
|
@@ -180,7 +252,7 @@ function formatDate(dateString) {
|
|
|
180
252
|
</div>
|
|
181
253
|
|
|
182
254
|
<h5 class="small card-title-text">
|
|
183
|
-
<a v-if="
|
|
255
|
+
<a v-if="resolvedLinkHref" :href="resolvedLinkHref">{{ title }}</a>
|
|
184
256
|
<span v-else>{{ title }}</span>
|
|
185
257
|
</h5>
|
|
186
258
|
|
|
@@ -193,7 +265,13 @@ function formatDate(dateString) {
|
|
|
193
265
|
<template v-for="chip in chips" :key="chip.text">
|
|
194
266
|
<a
|
|
195
267
|
v-if="chip.link || chip.href"
|
|
196
|
-
:href="
|
|
268
|
+
:href="
|
|
269
|
+
chip.link
|
|
270
|
+
? resolveUrl(chip.link)
|
|
271
|
+
: chip.href
|
|
272
|
+
? resolveUrl(chip.href)
|
|
273
|
+
: undefined
|
|
274
|
+
"
|
|
197
275
|
:style="getTagStyle(chip.text)"
|
|
198
276
|
class="chip small-chip border clickable-chip"
|
|
199
277
|
:class="chip.class"
|
|
@@ -211,7 +289,10 @@ function formatDate(dateString) {
|
|
|
211
289
|
</template>
|
|
212
290
|
</div>
|
|
213
291
|
|
|
214
|
-
<p
|
|
292
|
+
<p
|
|
293
|
+
v-if="metadata && metadata.length && !date && !authors.length"
|
|
294
|
+
class="small-text"
|
|
295
|
+
>
|
|
215
296
|
<span
|
|
216
297
|
v-for="(item, index) in metadata"
|
|
217
298
|
:key="index"
|
|
@@ -240,7 +321,7 @@ function formatDate(dateString) {
|
|
|
240
321
|
>
|
|
241
322
|
<a
|
|
242
323
|
v-if="link && !!link.text"
|
|
243
|
-
:href="
|
|
324
|
+
:href="resolvedLinkHref"
|
|
244
325
|
:target="link.target ?? '_blank'"
|
|
245
326
|
class="button transparent bold primary-text no-padding"
|
|
246
327
|
>
|
|
@@ -251,7 +332,7 @@ function formatDate(dateString) {
|
|
|
251
332
|
</a>
|
|
252
333
|
<a
|
|
253
334
|
v-if="secondaryLink"
|
|
254
|
-
:href="
|
|
335
|
+
:href="resolvedSecondaryLinkHref"
|
|
255
336
|
:target="secondaryLink.target ?? '_blank'"
|
|
256
337
|
class="button transparent primary-text no-padding secondary-link"
|
|
257
338
|
>
|
|
@@ -295,13 +376,17 @@ function formatDate(dateString) {
|
|
|
295
376
|
/* Clickable Cards Hover System */
|
|
296
377
|
.theme-feature-card.clickable-card {
|
|
297
378
|
cursor: pointer;
|
|
298
|
-
transition:
|
|
379
|
+
transition:
|
|
380
|
+
transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1),
|
|
381
|
+
box-shadow 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) !important;
|
|
299
382
|
will-change: transform, box-shadow;
|
|
300
383
|
}
|
|
301
384
|
|
|
302
385
|
.theme-feature-card.clickable-card:hover {
|
|
303
386
|
transform: translateY(-4px) !important;
|
|
304
|
-
box-shadow:
|
|
387
|
+
box-shadow:
|
|
388
|
+
0 12px 28px rgba(0, 0, 0, 0.12),
|
|
389
|
+
0 4px 8px rgba(0, 0, 0, 0.04) !important;
|
|
305
390
|
}
|
|
306
391
|
|
|
307
392
|
.small-chip {
|
|
@@ -474,6 +559,8 @@ function formatDate(dateString) {
|
|
|
474
559
|
}
|
|
475
560
|
|
|
476
561
|
/* card title overrides to make sure it respects the 20px request */
|
|
562
|
+
h5.small.card-title-text,
|
|
563
|
+
.card-title-text,
|
|
477
564
|
.VPHome .vp-doc h5.small.card-title-text {
|
|
478
565
|
font-size: 20px !important;
|
|
479
566
|
line-height: 1.4 !important;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { withBase } from "vitepress";
|
|
3
4
|
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
title: String,
|
|
@@ -41,6 +42,22 @@ const props = defineProps({
|
|
|
41
42
|
},
|
|
42
43
|
});
|
|
43
44
|
|
|
45
|
+
function resolveUrl(url) {
|
|
46
|
+
if (!url) return "";
|
|
47
|
+
if (url.startsWith("http") || url.startsWith("//")) return url;
|
|
48
|
+
const base = withBase("/");
|
|
49
|
+
if (base && base !== "/" && url.startsWith(base)) return url;
|
|
50
|
+
return url.startsWith("/") ? withBase(url) : withBase("/" + url);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
54
|
+
const resolvedLinkHref = computed(() =>
|
|
55
|
+
props.link?.href ? resolveUrl(props.link.href) : undefined,
|
|
56
|
+
);
|
|
57
|
+
const resolvedSecondaryLinkHref = computed(() =>
|
|
58
|
+
props.secondaryLink?.href ? resolveUrl(props.secondaryLink.href) : undefined,
|
|
59
|
+
);
|
|
60
|
+
|
|
44
61
|
const cardStyle = computed(() => ({
|
|
45
62
|
minHeight: props.minHeight,
|
|
46
63
|
}));
|
|
@@ -52,7 +69,12 @@ const cardStyle = computed(() => ({
|
|
|
52
69
|
:class="link && !link.text ? 'small-elevate' : 'no-elevate'"
|
|
53
70
|
:style="cardStyle"
|
|
54
71
|
>
|
|
55
|
-
<img
|
|
72
|
+
<img
|
|
73
|
+
v-if="image"
|
|
74
|
+
:src="resolvedImage"
|
|
75
|
+
alt=""
|
|
76
|
+
class="image-overlay-card-image"
|
|
77
|
+
/>
|
|
56
78
|
<div class="image-overlay-card-overlay"></div>
|
|
57
79
|
<div class="image-overlay-card-content padding">
|
|
58
80
|
<div style="padding-top: 3rem">
|
|
@@ -103,7 +125,7 @@ const cardStyle = computed(() => ({
|
|
|
103
125
|
<a
|
|
104
126
|
v-if="link && link.text"
|
|
105
127
|
class="button small white black-text"
|
|
106
|
-
:href="
|
|
128
|
+
:href="resolvedLinkHref"
|
|
107
129
|
:target="link.target ?? '_blank'"
|
|
108
130
|
>
|
|
109
131
|
{{ link.text }}
|
|
@@ -111,7 +133,7 @@ const cardStyle = computed(() => ({
|
|
|
111
133
|
<a
|
|
112
134
|
v-if="secondaryLink"
|
|
113
135
|
class="button small border white-text secondary-link"
|
|
114
|
-
:href="
|
|
136
|
+
:href="resolvedSecondaryLinkHref"
|
|
115
137
|
:target="secondaryLink.target ?? '_blank'"
|
|
116
138
|
>
|
|
117
139
|
{{ secondaryLink.text }}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { h } from "vue";
|
|
1
2
|
import FeatureSection from "./FeatureSection.vue";
|
|
2
3
|
|
|
3
4
|
describe("<FeatureSection />", () => {
|
|
@@ -60,4 +61,19 @@ describe("<FeatureSection />", () => {
|
|
|
60
61
|
cy.get(".feature-section").should("have.class", "dark");
|
|
61
62
|
cy.get(".feature-section").should("have.class", "reverse");
|
|
62
63
|
});
|
|
64
|
+
|
|
65
|
+
it("has padding-block-start 0 when following a title", () => {
|
|
66
|
+
const TestWrapper = {
|
|
67
|
+
render() {
|
|
68
|
+
return h("div", [
|
|
69
|
+
h("h2", "Products & Services"),
|
|
70
|
+
h("p", "Subtitle paragraph"),
|
|
71
|
+
h(FeatureSection, defaultProps),
|
|
72
|
+
]);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
cy.mount(TestWrapper);
|
|
77
|
+
cy.get(".feature-section").should("have.css", "padding-top", "0px");
|
|
78
|
+
});
|
|
63
79
|
});
|
|
@@ -120,16 +120,11 @@ const props = defineProps([
|
|
|
120
120
|
}
|
|
121
121
|
.feature-section.landing .holder {
|
|
122
122
|
padding: 32px 0;
|
|
123
|
-
background: var(--surface-container-
|
|
123
|
+
background: var(--surface-container-lowest, #ffffff);
|
|
124
124
|
}
|
|
125
125
|
.feature-section.dark .holder {
|
|
126
|
-
background:
|
|
127
|
-
background:
|
|
128
|
-
74.48% 130.95% at 50% -12.27%,
|
|
129
|
-
hsl(from var(--primary) h s calc(l + 7)) 0%,
|
|
130
|
-
var(--primary) 45%,
|
|
131
|
-
hsl(from var(--primary) h s calc(l - 20)) 100%
|
|
132
|
-
);
|
|
126
|
+
background: #000c14;
|
|
127
|
+
background: linear-gradient(180deg, var(--primary, #004170) 0%, #000c14 100%);
|
|
133
128
|
color: #fff;
|
|
134
129
|
}
|
|
135
130
|
.feature-section nav {
|