@eox/pages-theme-eox 2.0.0-alpha.8 → 2.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -383,6 +383,14 @@ if (!import.meta.env.SSR) {
|
|
|
383
383
|
}
|
|
384
384
|
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text.active,
|
|
385
385
|
.top-nav.has-hero.at-top .nav-desktop .button.text.active {
|
|
386
|
+
background-color: rgba(255, 255, 255, 0.15) !important;
|
|
387
|
+
color: #ffffff !important;
|
|
388
|
+
}
|
|
389
|
+
.Layout.layout-home .top-nav.at-top .nav-desktop .button.text.active:hover,
|
|
390
|
+
.top-nav.has-hero.at-top .nav-desktop .button.text.active:hover {
|
|
391
|
+
background-color: rgba(255, 255, 255, 0.25) !important;
|
|
392
|
+
}
|
|
393
|
+
.top-nav:not(.at-top) .nav-desktop .button.text.active {
|
|
386
394
|
background-color: var(--primary-container) !important;
|
|
387
395
|
color: var(--on-primary-container) !important;
|
|
388
396
|
}
|
|
@@ -394,6 +402,11 @@ if (!import.meta.env.SSR) {
|
|
|
394
402
|
}
|
|
395
403
|
.Layout.layout-home .top-nav.at-top .nav-desktop .button.border.active,
|
|
396
404
|
.top-nav.has-hero.at-top .nav-desktop .button.border.active {
|
|
405
|
+
background-color: rgba(255, 255, 255, 0.15) !important;
|
|
406
|
+
color: #ffffff !important;
|
|
407
|
+
border-color: transparent !important;
|
|
408
|
+
}
|
|
409
|
+
.top-nav:not(.at-top) .nav-desktop .button.border.active {
|
|
397
410
|
background-color: var(--primary-container) !important;
|
|
398
411
|
color: var(--on-primary-container) !important;
|
|
399
412
|
border-color: transparent !important;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
/>
|
|
21
21
|
</section>
|
|
22
22
|
|
|
23
|
-
<!-- Team Grid -
|
|
23
|
+
<!-- Team Grid - up to 4 columns on large screens -->
|
|
24
24
|
<div class="team-grid">
|
|
25
25
|
<FeatureCard
|
|
26
26
|
v-for="member in sortedTeamMembers"
|
|
@@ -126,7 +126,7 @@ const memberHasPosts = (memberId) => {
|
|
|
126
126
|
<style scoped>
|
|
127
127
|
.team-page {
|
|
128
128
|
width: 100%;
|
|
129
|
-
max-width:
|
|
129
|
+
max-width: 1400px !important;
|
|
130
130
|
margin-left: auto !important;
|
|
131
131
|
margin-right: auto !important;
|
|
132
132
|
padding-left: 1rem !important;
|
|
@@ -245,6 +245,12 @@ const memberHasPosts = (memberId) => {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
@media (min-width: 1280px) {
|
|
249
|
+
.team-grid {
|
|
250
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
248
254
|
/* Deep overrides to integrate with theme-provided FeatureCard */
|
|
249
255
|
.team-member-card {
|
|
250
256
|
height: 100% !important;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import TextSection from "./TextSection.vue";
|
|
2
|
+
|
|
3
|
+
describe("<TextSection />", () => {
|
|
4
|
+
it("renders title, tagline and button", () => {
|
|
5
|
+
cy.mount(TextSection, {
|
|
6
|
+
props: {
|
|
7
|
+
title: "Test Section",
|
|
8
|
+
tagline: "Test Tagline",
|
|
9
|
+
buttonText: "Click Me",
|
|
10
|
+
buttonLink: "https://example.com",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
cy.contains("Test Section").should("exist");
|
|
15
|
+
cy.contains("Test Tagline").should("exist");
|
|
16
|
+
cy.contains("a.button", "Click Me")
|
|
17
|
+
.should("have.attr", "href", "https://example.com")
|
|
18
|
+
.and("have.attr", "target", "_blank");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("renders slot content and image", () => {
|
|
22
|
+
cy.mount(TextSection, {
|
|
23
|
+
props: {
|
|
24
|
+
image: "/test.jpg",
|
|
25
|
+
imageAlt: "Test Image",
|
|
26
|
+
},
|
|
27
|
+
slots: {
|
|
28
|
+
default: "<p>This is paragraph text.</p>",
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
cy.contains("This is paragraph text.").should("exist");
|
|
33
|
+
cy.get("img.section-image")
|
|
34
|
+
.should("have.attr", "src", "/test.jpg")
|
|
35
|
+
.and("have.attr", "alt", "Test Image");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("applies reverse class when reverse prop is true", () => {
|
|
39
|
+
cy.mount(TextSection, {
|
|
40
|
+
props: {
|
|
41
|
+
image: "/test.jpg",
|
|
42
|
+
reverse: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
cy.get(".text-section").should("have.class", "is-reverse");
|
|
47
|
+
cy.get(".text-section-grid").should("have.class", "grid-reverse");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("applies single column styling when no image is provided", () => {
|
|
51
|
+
cy.mount(TextSection, {
|
|
52
|
+
slots: {
|
|
53
|
+
default: "<p>Single column text</p>",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
cy.get(".text-section").should("have.class", "no-image");
|
|
58
|
+
cy.get(".text-section-grid").should("have.class", "grid-single");
|
|
59
|
+
cy.get(".media-column").should("not.exist");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="text-section full-width"
|
|
4
|
+
:class="{
|
|
5
|
+
'is-reverse': reverse,
|
|
6
|
+
'no-image': !image && !$slots.media,
|
|
7
|
+
'is-compact': compact,
|
|
8
|
+
}"
|
|
9
|
+
>
|
|
10
|
+
<div class="holder">
|
|
11
|
+
<div
|
|
12
|
+
class="text-section-grid"
|
|
13
|
+
:class="{
|
|
14
|
+
'grid-reverse': reverse,
|
|
15
|
+
'grid-single': !image && !$slots.media,
|
|
16
|
+
}"
|
|
17
|
+
>
|
|
18
|
+
<!-- Text Column -->
|
|
19
|
+
<div class="text-column">
|
|
20
|
+
<span v-if="tagline" class="tagline">{{ tagline }}</span>
|
|
21
|
+
<h2 v-if="title" class="section-title">{{ title }}</h2>
|
|
22
|
+
<div class="section-content">
|
|
23
|
+
<slot></slot>
|
|
24
|
+
</div>
|
|
25
|
+
<!-- Prop-based button -->
|
|
26
|
+
<div v-if="buttonText && buttonLink" class="section-actions">
|
|
27
|
+
<a
|
|
28
|
+
:href="resolvedButtonLink"
|
|
29
|
+
:target="resolvedButtonTarget"
|
|
30
|
+
class="button"
|
|
31
|
+
@click="
|
|
32
|
+
trackEvent([
|
|
33
|
+
'TextSection',
|
|
34
|
+
'Click',
|
|
35
|
+
title || buttonText,
|
|
36
|
+
buttonText,
|
|
37
|
+
])
|
|
38
|
+
"
|
|
39
|
+
>
|
|
40
|
+
<span>{{ buttonText }}</span>
|
|
41
|
+
<i v-if="buttonIcon" :class="buttonIcon" class="btn-icon"></i>
|
|
42
|
+
</a>
|
|
43
|
+
</div>
|
|
44
|
+
<slot name="actions"></slot>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<!-- Media Column -->
|
|
48
|
+
<div v-if="image || $slots.media" class="media-column">
|
|
49
|
+
<slot name="media">
|
|
50
|
+
<img
|
|
51
|
+
v-if="image"
|
|
52
|
+
:src="resolvedImage"
|
|
53
|
+
:alt="imageAlt || title || ''"
|
|
54
|
+
class="section-image"
|
|
55
|
+
loading="lazy"
|
|
56
|
+
/>
|
|
57
|
+
</slot>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<script setup>
|
|
65
|
+
import { withBase } from "vitepress";
|
|
66
|
+
import { computed } from "vue";
|
|
67
|
+
import { trackEvent } from "../helpers.js";
|
|
68
|
+
|
|
69
|
+
const props = defineProps({
|
|
70
|
+
title: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: "",
|
|
73
|
+
},
|
|
74
|
+
tagline: {
|
|
75
|
+
type: String,
|
|
76
|
+
default: "",
|
|
77
|
+
},
|
|
78
|
+
image: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: "",
|
|
81
|
+
},
|
|
82
|
+
imageAlt: {
|
|
83
|
+
type: String,
|
|
84
|
+
default: "",
|
|
85
|
+
},
|
|
86
|
+
reverse: {
|
|
87
|
+
type: Boolean,
|
|
88
|
+
default: false,
|
|
89
|
+
},
|
|
90
|
+
buttonText: {
|
|
91
|
+
type: String,
|
|
92
|
+
default: "",
|
|
93
|
+
},
|
|
94
|
+
buttonLink: {
|
|
95
|
+
type: String,
|
|
96
|
+
default: "",
|
|
97
|
+
},
|
|
98
|
+
buttonTarget: {
|
|
99
|
+
type: String,
|
|
100
|
+
default: "",
|
|
101
|
+
},
|
|
102
|
+
buttonIcon: {
|
|
103
|
+
type: String,
|
|
104
|
+
default: "",
|
|
105
|
+
},
|
|
106
|
+
compact: {
|
|
107
|
+
type: Boolean,
|
|
108
|
+
default: false,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
function resolveUrl(url) {
|
|
113
|
+
if (!url) return "";
|
|
114
|
+
if (
|
|
115
|
+
url.startsWith("http://") ||
|
|
116
|
+
url.startsWith("https://") ||
|
|
117
|
+
url.startsWith("//") ||
|
|
118
|
+
url.startsWith("#")
|
|
119
|
+
) {
|
|
120
|
+
return url;
|
|
121
|
+
}
|
|
122
|
+
return withBase(url);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const resolvedImage = computed(() => resolveUrl(props.image));
|
|
126
|
+
const resolvedButtonLink = computed(() => resolveUrl(props.buttonLink));
|
|
127
|
+
const resolvedButtonTarget = computed(() => {
|
|
128
|
+
if (props.buttonTarget) return props.buttonTarget;
|
|
129
|
+
if (
|
|
130
|
+
props.buttonLink &&
|
|
131
|
+
(props.buttonLink.startsWith("http://") ||
|
|
132
|
+
props.buttonLink.startsWith("https://") ||
|
|
133
|
+
props.buttonLink.startsWith("//"))
|
|
134
|
+
) {
|
|
135
|
+
return "_blank";
|
|
136
|
+
}
|
|
137
|
+
return "_self";
|
|
138
|
+
});
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style scoped>
|
|
142
|
+
.full-width {
|
|
143
|
+
width: 100svw;
|
|
144
|
+
position: relative;
|
|
145
|
+
left: 50%;
|
|
146
|
+
right: 50%;
|
|
147
|
+
margin-left: -50svw;
|
|
148
|
+
margin-right: -50svw;
|
|
149
|
+
max-inline-size: unset !important;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.text-section {
|
|
153
|
+
padding-block: 56px;
|
|
154
|
+
display: flex;
|
|
155
|
+
justify-content: center;
|
|
156
|
+
background: transparent;
|
|
157
|
+
color: var(--on-surface, #020617);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.text-section.is-compact {
|
|
161
|
+
padding-block: 28px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* When two text-sections are placed consecutively, tighten the spacing */
|
|
165
|
+
:deep(.text-section + .text-section),
|
|
166
|
+
.text-section + .text-section {
|
|
167
|
+
padding-block-start: 16px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.text-section .holder {
|
|
171
|
+
width: 100%;
|
|
172
|
+
max-width: 1200px;
|
|
173
|
+
margin-left: auto;
|
|
174
|
+
margin-right: auto;
|
|
175
|
+
padding-inline: 24px;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.text-section-grid {
|
|
179
|
+
display: grid;
|
|
180
|
+
grid-template-columns: 1fr;
|
|
181
|
+
gap: 2rem;
|
|
182
|
+
align-items: center;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* On mobile (< 1024px), the media/image column is always at the start before the text */
|
|
186
|
+
.text-section-grid .media-column {
|
|
187
|
+
order: 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.text-section-grid .text-column {
|
|
191
|
+
order: 2;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@media (min-width: 1024px) {
|
|
195
|
+
.text-section-grid {
|
|
196
|
+
grid-template-columns: 1fr 1fr;
|
|
197
|
+
gap: 4rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* Default on desktop: Text on left (1), Media on right (2) */
|
|
201
|
+
.text-section-grid .text-column {
|
|
202
|
+
order: 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.text-section-grid .media-column {
|
|
206
|
+
order: 2;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Reverse on desktop: Media on left (1), Text on right (2) */
|
|
210
|
+
.text-section-grid.grid-reverse .text-column {
|
|
211
|
+
order: 2;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.text-section-grid.grid-reverse .media-column {
|
|
215
|
+
order: 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.text-section-grid.grid-single {
|
|
219
|
+
grid-template-columns: 1fr;
|
|
220
|
+
max-width: 680px;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.text-column {
|
|
225
|
+
display: flex;
|
|
226
|
+
flex-direction: column;
|
|
227
|
+
width: 100%;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.tagline {
|
|
231
|
+
display: inline-block;
|
|
232
|
+
font-size: 0.875rem;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
text-transform: uppercase;
|
|
235
|
+
letter-spacing: 0.05em;
|
|
236
|
+
color: var(--primary, #004170);
|
|
237
|
+
margin-bottom: 0.5rem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.section-title {
|
|
241
|
+
font-family: inherit;
|
|
242
|
+
font-weight: 700;
|
|
243
|
+
font-size: 2rem;
|
|
244
|
+
line-height: 1.25;
|
|
245
|
+
color: #020617;
|
|
246
|
+
margin-top: 0;
|
|
247
|
+
margin-bottom: 1.25rem;
|
|
248
|
+
border-top: none;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* Markdown Content Styling */
|
|
252
|
+
.section-content :deep(h2) {
|
|
253
|
+
font-family: inherit;
|
|
254
|
+
font-weight: 700;
|
|
255
|
+
font-size: 2rem;
|
|
256
|
+
line-height: 1.25;
|
|
257
|
+
color: #020617;
|
|
258
|
+
margin-top: 0;
|
|
259
|
+
margin-bottom: 1.25rem;
|
|
260
|
+
border-top: none;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.section-content :deep(h3) {
|
|
264
|
+
font-family: inherit;
|
|
265
|
+
font-weight: 700;
|
|
266
|
+
font-size: 1.5rem;
|
|
267
|
+
line-height: 1.35;
|
|
268
|
+
color: #020617;
|
|
269
|
+
margin-top: 1.5rem;
|
|
270
|
+
margin-bottom: 0.75rem;
|
|
271
|
+
border-top: none;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.section-content :deep(h4),
|
|
275
|
+
.section-content :deep(h5) {
|
|
276
|
+
font-family: inherit;
|
|
277
|
+
font-weight: 600;
|
|
278
|
+
font-size: 1.125rem;
|
|
279
|
+
line-height: 1.4;
|
|
280
|
+
color: #020617;
|
|
281
|
+
margin-top: 1.25rem;
|
|
282
|
+
margin-bottom: 0.5rem;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
.section-content :deep(p) {
|
|
286
|
+
font-size: 1rem;
|
|
287
|
+
line-height: 1.65;
|
|
288
|
+
color: #334155;
|
|
289
|
+
margin-top: 0;
|
|
290
|
+
margin-bottom: 1rem;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.section-content :deep(ul) {
|
|
294
|
+
margin-top: 0.75rem;
|
|
295
|
+
margin-bottom: 1.25rem;
|
|
296
|
+
padding-left: 1.25rem;
|
|
297
|
+
list-style-type: disc;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.section-content :deep(li) {
|
|
301
|
+
font-size: 0.95rem;
|
|
302
|
+
line-height: 1.6;
|
|
303
|
+
color: #334155;
|
|
304
|
+
margin-bottom: 0.5rem;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.section-content :deep(li strong) {
|
|
308
|
+
color: #020617;
|
|
309
|
+
font-weight: 700;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.section-content :deep(a:not(.button)) {
|
|
313
|
+
color: #0284c7;
|
|
314
|
+
text-decoration: none;
|
|
315
|
+
font-weight: 500;
|
|
316
|
+
transition: color 0.15s ease-in-out;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.section-content :deep(a:not(.button):hover) {
|
|
320
|
+
text-decoration: underline;
|
|
321
|
+
color: #0369a1;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/* Pill Button Styling - for both prop and markdown links with .button */
|
|
325
|
+
.section-actions {
|
|
326
|
+
margin-top: 0.75rem;
|
|
327
|
+
margin-bottom: 0.5rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.button,
|
|
331
|
+
.section-content :deep(a.button),
|
|
332
|
+
.section-content :deep(.button) {
|
|
333
|
+
display: inline-flex;
|
|
334
|
+
align-items: center;
|
|
335
|
+
justify-content: center;
|
|
336
|
+
gap: 0.5rem;
|
|
337
|
+
padding: 0.5rem 1.5rem;
|
|
338
|
+
border-radius: 9999px;
|
|
339
|
+
border: 1px solid #020617;
|
|
340
|
+
background-color: #ffffff;
|
|
341
|
+
color: #020617 !important;
|
|
342
|
+
font-weight: 600;
|
|
343
|
+
font-size: 0.9375rem;
|
|
344
|
+
line-height: 1.5;
|
|
345
|
+
text-decoration: none !important;
|
|
346
|
+
transition: all 0.2s ease-in-out;
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
width: fit-content;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.button:hover,
|
|
352
|
+
.section-content :deep(a.button:hover),
|
|
353
|
+
.section-content :deep(.button:hover) {
|
|
354
|
+
background-color: #f1f5f9;
|
|
355
|
+
border-color: #020617;
|
|
356
|
+
color: #020617 !important;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.btn-icon {
|
|
360
|
+
font-size: 1.1em;
|
|
361
|
+
display: inline-flex;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/* Media Column & Image Styling */
|
|
365
|
+
.media-column {
|
|
366
|
+
display: flex;
|
|
367
|
+
justify-content: center;
|
|
368
|
+
align-items: center;
|
|
369
|
+
width: 100%;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.section-image {
|
|
373
|
+
width: 100%;
|
|
374
|
+
max-width: 100%;
|
|
375
|
+
height: auto;
|
|
376
|
+
border-radius: 16px;
|
|
377
|
+
border: 1px solid rgba(15, 23, 42, 0.08);
|
|
378
|
+
box-shadow:
|
|
379
|
+
0 4px 16px -2px rgba(15, 23, 42, 0.06),
|
|
380
|
+
0 2px 6px -2px rgba(15, 23, 42, 0.04);
|
|
381
|
+
background-color: #ffffff;
|
|
382
|
+
display: block;
|
|
383
|
+
}
|
|
384
|
+
</style>
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@ import BlogList from "./components/BlogList.vue";
|
|
|
23
23
|
import TeamPage from "./components/TeamPage.vue";
|
|
24
24
|
import SectionHeader from "./components/SectionHeader.vue";
|
|
25
25
|
import TestimonialsSection from "./components/TestimonialsSection.vue";
|
|
26
|
+
import TextSection from "./components/TextSection.vue";
|
|
26
27
|
import Layout from "./Layout.vue";
|
|
27
28
|
import "./style.css";
|
|
28
29
|
|
|
@@ -55,6 +56,7 @@ export default {
|
|
|
55
56
|
app.component("TeamPage", TeamPage);
|
|
56
57
|
app.component("SectionHeader", SectionHeader);
|
|
57
58
|
app.component("TestimonialsSection", TestimonialsSection);
|
|
59
|
+
app.component("TextSection", TextSection);
|
|
58
60
|
|
|
59
61
|
router.onAfterRouteChanged = () => {
|
|
60
62
|
if (!import.meta.env.SSR) {
|