@eox/pages-theme-eox 1.0.0 → 1.1.1
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/CHANGELOG.md +17 -0
- package/README.md +40 -1
- package/cypress/support/mocks/helpers.js +15 -0
- package/package.json +1 -1
- package/src/components/CookieBanner.vue +19 -6
- package/src/components/CookieSettings.vue +35 -27
- package/src/components/DataTable.vue +5 -1
- package/src/components/FeatureSection.vue +8 -4
- package/src/components/Footer.cy.js +58 -0
- package/src/components/Footer.vue +10 -18
- package/src/components/MobileNavDropdown.vue +6 -5
- package/src/components/NavBar.cy.js +37 -0
- package/src/components/NavBar.vue +92 -8
- package/src/components/NavDropdown.vue +7 -6
- package/src/components/NotFound.vue +8 -2
- package/src/components/PricingTable.vue +24 -7
- package/src/components/Tutorial.cy.js +85 -0
- package/src/components/Tutorial.vue +162 -0
- package/src/helpers.js +25 -0
- package/src/index.js +2 -0
- package/src/vitepressConfig.mjs +147 -95
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<li v-for="item in theme.nav.filter((item) => !item.action)">
|
|
37
37
|
<details v-if="item.items">
|
|
38
38
|
<summary>
|
|
39
|
-
<span class="max">{{ item.text }}</span>
|
|
39
|
+
<span class="max">{{ t(item.text, theme.i18n) }}</span>
|
|
40
40
|
<i class="mdi mdi-chevron-down"></i>
|
|
41
41
|
</summary>
|
|
42
42
|
<ul class="list">
|
|
@@ -51,9 +51,31 @@
|
|
|
51
51
|
:rel="item.rel"
|
|
52
52
|
:class="item.action ? 'button large medium-elevate cta' : ''"
|
|
53
53
|
>
|
|
54
|
-
<span>{{ item.text }}</span>
|
|
54
|
+
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
55
55
|
</a>
|
|
56
56
|
</li>
|
|
57
|
+
<li v-if="langs && langs.length > 1">
|
|
58
|
+
<details>
|
|
59
|
+
<summary>
|
|
60
|
+
<i class="mdi mdi-translate small"></i>
|
|
61
|
+
<span class="max">{{
|
|
62
|
+
langs.find((l) => l.active)?.label
|
|
63
|
+
}}</span>
|
|
64
|
+
<i class="mdi mdi-chevron-down small"></i>
|
|
65
|
+
</summary>
|
|
66
|
+
<ul class="list">
|
|
67
|
+
<li
|
|
68
|
+
v-for="lang in langs.filter((l) => !l.active)"
|
|
69
|
+
:key="lang.link"
|
|
70
|
+
:class="{ active: lang.active }"
|
|
71
|
+
>
|
|
72
|
+
<a :href="withBase(lang.link)" data-ui="#mobile-menu">
|
|
73
|
+
<span>{{ lang.label }}</span>
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
</ul>
|
|
77
|
+
</details>
|
|
78
|
+
</li>
|
|
57
79
|
</ul>
|
|
58
80
|
<div class="grid">
|
|
59
81
|
<div class="s12">
|
|
@@ -69,7 +91,7 @@
|
|
|
69
91
|
: 'primary-text'
|
|
70
92
|
"
|
|
71
93
|
>
|
|
72
|
-
<span>{{ item.text }}</span>
|
|
94
|
+
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
73
95
|
<i
|
|
74
96
|
v-if="
|
|
75
97
|
item.action === 'primary' || item.action === 'secondary'
|
|
@@ -119,7 +141,7 @@
|
|
|
119
141
|
class="button text"
|
|
120
142
|
:class="{ active: isActive(item, route.path) }"
|
|
121
143
|
>
|
|
122
|
-
<span>{{ item.text }}</span>
|
|
144
|
+
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
123
145
|
<i class="mdi mdi-chevron-down"></i>
|
|
124
146
|
<menu class="no-wrap surface-container-lowest">
|
|
125
147
|
<NavDropdown :items="item.items" />
|
|
@@ -132,7 +154,7 @@
|
|
|
132
154
|
:href="withBase(item.link)"
|
|
133
155
|
:target="item.target"
|
|
134
156
|
:rel="item.rel"
|
|
135
|
-
>{{ item.text }}</a
|
|
157
|
+
>{{ t(item.text, theme.i18n) }}</a
|
|
136
158
|
>
|
|
137
159
|
</li>
|
|
138
160
|
</ul>
|
|
@@ -153,7 +175,7 @@
|
|
|
153
175
|
:rel="item.rel"
|
|
154
176
|
@click="trackEvent(['CTA', 'Click', 'Nav', item.text])"
|
|
155
177
|
>
|
|
156
|
-
<span>{{ item.text }}</span>
|
|
178
|
+
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
157
179
|
<i
|
|
158
180
|
v-if="item.action === 'primary' || item.action === 'secondary'"
|
|
159
181
|
class="mdi mdi-arrow-right"
|
|
@@ -173,20 +195,79 @@
|
|
|
173
195
|
</a>
|
|
174
196
|
</li>
|
|
175
197
|
</ul>
|
|
198
|
+
<ul class="left-align no-margin" v-if="langs && langs.length > 1">
|
|
199
|
+
<li>
|
|
200
|
+
<button class="button text">
|
|
201
|
+
<i class="mdi mdi-translate small"></i>
|
|
202
|
+
<i class="mdi mdi-chevron-down small"></i>
|
|
203
|
+
<menu class="no-wrap surface-container-lowest">
|
|
204
|
+
<li
|
|
205
|
+
v-for="lang in langs"
|
|
206
|
+
:key="lang.link"
|
|
207
|
+
:class="{ active: lang.active }"
|
|
208
|
+
>
|
|
209
|
+
<a :href="withBase(lang.link)">
|
|
210
|
+
<span>{{ lang.label }}</span>
|
|
211
|
+
</a>
|
|
212
|
+
</li>
|
|
213
|
+
</menu>
|
|
214
|
+
</button>
|
|
215
|
+
</li>
|
|
216
|
+
</ul>
|
|
176
217
|
</nav>
|
|
177
218
|
</nav>
|
|
178
219
|
</div>
|
|
179
220
|
</template>
|
|
180
221
|
|
|
181
222
|
<script setup>
|
|
223
|
+
import { computed } from "vue";
|
|
182
224
|
import { useData, useRoute, withBase } from "vitepress";
|
|
183
|
-
import { trackEvent, isActive } from "../helpers";
|
|
225
|
+
import { t, trackEvent, isActive } from "../helpers";
|
|
184
226
|
import NavDropdown from "./NavDropdown.vue";
|
|
185
227
|
import MobileNavDropdown from "./MobileNavDropdown.vue";
|
|
186
228
|
|
|
187
|
-
const { site, theme } = useData();
|
|
229
|
+
const { site, theme, localeIndex } = useData();
|
|
188
230
|
const route = useRoute();
|
|
189
231
|
|
|
232
|
+
const langs = computed(() => {
|
|
233
|
+
const locales = site.value.locales;
|
|
234
|
+
if (!locales || Object.keys(locales).length <= 1) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
return Object.entries(locales).map(([id, locale]) => {
|
|
238
|
+
const currentPath = route.path;
|
|
239
|
+
const base = withBase("");
|
|
240
|
+
let pathRelativeToBase = currentPath;
|
|
241
|
+
if (base !== "/" && currentPath.startsWith(base)) {
|
|
242
|
+
pathRelativeToBase = currentPath.substring(base.length);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const currentLocaleId = localeIndex.value;
|
|
246
|
+
const currentLocalePrefix =
|
|
247
|
+
currentLocaleId === "root" ? "" : `/${currentLocaleId}`;
|
|
248
|
+
|
|
249
|
+
let pathWithoutLocale = pathRelativeToBase;
|
|
250
|
+
if (
|
|
251
|
+
currentLocalePrefix &&
|
|
252
|
+
pathRelativeToBase.startsWith(currentLocalePrefix)
|
|
253
|
+
) {
|
|
254
|
+
pathWithoutLocale = pathRelativeToBase.substring(
|
|
255
|
+
currentLocalePrefix.length,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const targetLocalePrefix = id === "root" ? "" : `/${id}`;
|
|
260
|
+
let link = `${targetLocalePrefix}${pathWithoutLocale}`.replace(/\/+/g, "/");
|
|
261
|
+
if (!link) link = "/";
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
label: locale.label,
|
|
265
|
+
link: link,
|
|
266
|
+
active: id === currentLocaleId,
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
190
271
|
if (!import.meta.env.SSR) {
|
|
191
272
|
const scrollListener = () => {
|
|
192
273
|
const nav = document.querySelector(".top-nav");
|
|
@@ -289,4 +370,7 @@ nav.nav-desktop {
|
|
|
289
370
|
display: flex;
|
|
290
371
|
}
|
|
291
372
|
}
|
|
373
|
+
details summary i.mdi-translate {
|
|
374
|
+
transform: rotate(0deg) !important;
|
|
375
|
+
}
|
|
292
376
|
</style>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
class="row max padding"
|
|
10
10
|
:class="{ active: isActive(item, route.path) }"
|
|
11
11
|
>
|
|
12
|
-
<span class="max">{{ item.text }}</span>
|
|
12
|
+
<span class="max">{{ t(item.text, theme.i18n) }}</span>
|
|
13
13
|
<i class="mdi mdi-chevron-right"></i>
|
|
14
14
|
</a>
|
|
15
15
|
<a
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
class="row max padding"
|
|
18
18
|
:class="{ active: isActive(item, route.path) }"
|
|
19
19
|
>
|
|
20
|
-
<span class="max">{{ item.text }}</span>
|
|
20
|
+
<span class="max">{{ t(item.text, theme.i18n) }}</span>
|
|
21
21
|
<i class="mdi mdi-chevron-right"></i>
|
|
22
22
|
</a>
|
|
23
23
|
<menu class="no-wrap surface-container-lowest">
|
|
@@ -34,21 +34,21 @@
|
|
|
34
34
|
class="row"
|
|
35
35
|
:class="{ active: isActive(item, route.path) }"
|
|
36
36
|
>
|
|
37
|
-
<span>{{ item.text }}</span>
|
|
37
|
+
<span>{{ t(item.text, theme.i18n) }}</span>
|
|
38
38
|
</a>
|
|
39
39
|
<span
|
|
40
40
|
v-else
|
|
41
41
|
class="row"
|
|
42
42
|
:class="{ active: isActive(item, route.path) }"
|
|
43
|
-
>{{ item.text }}</span
|
|
43
|
+
>{{ t(item.text, theme.i18n) }}</span
|
|
44
44
|
>
|
|
45
45
|
</li>
|
|
46
46
|
</template>
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script setup>
|
|
50
|
-
import { withBase, useRoute } from "vitepress";
|
|
51
|
-
import { isActive } from "../helpers";
|
|
50
|
+
import { withBase, useRoute, useData } from "vitepress";
|
|
51
|
+
import { isActive, t } from "../helpers";
|
|
52
52
|
|
|
53
53
|
defineProps({
|
|
54
54
|
items: {
|
|
@@ -58,4 +58,5 @@ defineProps({
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
const route = useRoute();
|
|
61
|
+
const { theme } = useData();
|
|
61
62
|
</script>
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="VPPage">
|
|
3
3
|
<h1>404</h1>
|
|
4
|
-
<p>The page you requested was not found
|
|
4
|
+
<p>{{ t("The page you requested was not found.", theme.i18n) }}</p>
|
|
5
5
|
<div class="small-space"></div>
|
|
6
6
|
<nav>
|
|
7
7
|
<a class="button responsive-mobile" href="/">
|
|
8
8
|
<i class="mdi mdi-arrow-left"></i>
|
|
9
|
-
<span>Back to home</span>
|
|
9
|
+
<span>{{ t("Back to home", theme.i18n) }}</span>
|
|
10
10
|
</a>
|
|
11
11
|
</nav>
|
|
12
12
|
<div class="large-space"></div>
|
|
13
13
|
</div>
|
|
14
14
|
</template>
|
|
15
|
+
|
|
16
|
+
<script setup>
|
|
17
|
+
import { useData } from "vitepress";
|
|
18
|
+
import { t } from "../helpers";
|
|
19
|
+
const { theme } = useData();
|
|
20
|
+
</script>
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
:href="addPlanConfig(contactLink, plan)"
|
|
49
49
|
class="button responsive bold margin-top-1 margin-bottom-2"
|
|
50
50
|
>
|
|
51
|
-
Contact us
|
|
51
|
+
{{ t("Contact us", theme.i18n) }}
|
|
52
52
|
</a>
|
|
53
53
|
</ClientOnly>
|
|
54
54
|
</div>
|
|
@@ -58,7 +58,9 @@
|
|
|
58
58
|
<!-- Main Plans Table -->
|
|
59
59
|
<div class="wrapper" :style="gridStyle">
|
|
60
60
|
<div class="cell orig-col-1 l top-margin">
|
|
61
|
-
<h6 v-if="!localDetails.length" class="bold small">
|
|
61
|
+
<h6 v-if="!localDetails.length" class="bold small">
|
|
62
|
+
{{ t("Plans:", theme.i18n) }}
|
|
63
|
+
</h6>
|
|
62
64
|
</div>
|
|
63
65
|
<div
|
|
64
66
|
v-for="(plan, index) in localPlans"
|
|
@@ -69,7 +71,9 @@
|
|
|
69
71
|
<h6 class="primary-text bold top-margin">{{ plan.name }}</h6>
|
|
70
72
|
</div>
|
|
71
73
|
|
|
72
|
-
<div class="cell orig-col-1 l">
|
|
74
|
+
<div class="cell orig-col-1 l">
|
|
75
|
+
{{ t("Price (per month):", theme.i18n) }}
|
|
76
|
+
</div>
|
|
73
77
|
<div
|
|
74
78
|
v-for="(plan, index) in localPlans"
|
|
75
79
|
:key="'price-' + index"
|
|
@@ -142,7 +146,7 @@
|
|
|
142
146
|
:class="`surface-container-low cell bottom-cell orig-col-${index + 2} center-align`"
|
|
143
147
|
>
|
|
144
148
|
<a v-if="plan.link" :href="plan.link"
|
|
145
|
-
>See all features
|
|
149
|
+
>{{ t("See all features", theme.i18n) }}
|
|
146
150
|
<svg
|
|
147
151
|
style="width: 16px; height: 16px"
|
|
148
152
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -168,7 +172,7 @@
|
|
|
168
172
|
v-if="showSales"
|
|
169
173
|
:href="addPlanConfig(contactLink, plan)"
|
|
170
174
|
class="button responsive bold"
|
|
171
|
-
>Contact us</a
|
|
175
|
+
>{{ t("Contact us", theme.i18n) }}</a
|
|
172
176
|
>
|
|
173
177
|
</ClientOnly>
|
|
174
178
|
</div>
|
|
@@ -185,7 +189,7 @@
|
|
|
185
189
|
|
|
186
190
|
<div class="wrapper" :style="secondaryGridStyle">
|
|
187
191
|
<div class="cell cell orig-col-1 l">
|
|
188
|
-
<div class="">Additional price
|
|
192
|
+
<div class="">{{ t("Additional price:", theme.i18n) }}</div>
|
|
189
193
|
</div>
|
|
190
194
|
<div
|
|
191
195
|
v-for="(plan, detailPlanIndex) in detail.plans"
|
|
@@ -231,15 +235,28 @@
|
|
|
231
235
|
</template>
|
|
232
236
|
|
|
233
237
|
<p class="small grey-text m-4" v-if="showVAT">
|
|
234
|
-
*
|
|
238
|
+
*
|
|
239
|
+
{{
|
|
240
|
+
t(
|
|
241
|
+
"All prices are given excluding VAT. Prices are valid until",
|
|
242
|
+
theme.i18n,
|
|
243
|
+
)
|
|
244
|
+
}}
|
|
235
245
|
{{ showVAT }}.
|
|
236
246
|
</p>
|
|
237
247
|
</div>
|
|
238
248
|
</template>
|
|
239
249
|
|
|
240
250
|
<script>
|
|
251
|
+
import { t } from "../helpers.js";
|
|
252
|
+
import { useData } from "vitepress";
|
|
253
|
+
|
|
241
254
|
export default {
|
|
242
255
|
name: "PricingTable",
|
|
256
|
+
setup() {
|
|
257
|
+
const { theme } = useData();
|
|
258
|
+
return { theme, t };
|
|
259
|
+
},
|
|
243
260
|
props: {
|
|
244
261
|
config: {
|
|
245
262
|
type: Object,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import Tutorial from "./Tutorial.vue";
|
|
2
|
+
|
|
3
|
+
describe("<Tutorial />", () => {
|
|
4
|
+
it("renders with all slots provided", () => {
|
|
5
|
+
cy.mount(Tutorial, {
|
|
6
|
+
slots: {
|
|
7
|
+
default: "<p>Learn how to configure the map component.</p>",
|
|
8
|
+
demo: "<div class='demo-content'>Interactive Map Demo</div>",
|
|
9
|
+
code: "<pre><code>const map = new Map();</code></pre>",
|
|
10
|
+
controls: "<button>Reset</button>",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
cy.get(".description").should(
|
|
15
|
+
"contain.text",
|
|
16
|
+
"Learn how to configure the map component.",
|
|
17
|
+
);
|
|
18
|
+
cy.get(".demo-wrapper").should("exist");
|
|
19
|
+
cy.get(".demo-content").should("contain.text", "Interactive Map Demo");
|
|
20
|
+
cy.get(".code-view code").should("contain.text", "const map = new Map()");
|
|
21
|
+
cy.get("button").should("contain.text", "Reset");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("supports both default and named description slot", () => {
|
|
25
|
+
cy.mount(Tutorial, {
|
|
26
|
+
slots: {
|
|
27
|
+
description: "<p>Using named slot</p>",
|
|
28
|
+
demo: "<div>Demo</div>",
|
|
29
|
+
code: "<pre>Code</pre>",
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
cy.get(".description").should("contain.text", "Using named slot");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("renders with only required slots", () => {
|
|
37
|
+
cy.mount(Tutorial, {
|
|
38
|
+
slots: {
|
|
39
|
+
demo: "<div>Demo Only</div>",
|
|
40
|
+
code: "<pre><code>console.log('test');</code></pre>",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
cy.get(".description").should("not.exist");
|
|
45
|
+
cy.get(".demo-wrapper").should("exist");
|
|
46
|
+
cy.get(".code-view").should("exist");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("applies custom demoHeight prop", () => {
|
|
50
|
+
cy.mount(Tutorial, {
|
|
51
|
+
props: {
|
|
52
|
+
demoHeight: "700px",
|
|
53
|
+
},
|
|
54
|
+
slots: {
|
|
55
|
+
demo: "<div>Tall Demo</div>",
|
|
56
|
+
code: "<pre>Code</pre>",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
cy.get(".demo-wrapper").should("have.css", "height", "700px");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("renders with code examples correctly", () => {
|
|
64
|
+
cy.mount(Tutorial, {
|
|
65
|
+
slots: {
|
|
66
|
+
default: "<p>Map configuration example</p>",
|
|
67
|
+
demo: "<div>Map Component</div>",
|
|
68
|
+
code: `<pre><code>import "@eox/map";
|
|
69
|
+
|
|
70
|
+
const config = {
|
|
71
|
+
center: [0, 0],
|
|
72
|
+
zoom: 5
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const map = document.querySelector("eox-map");
|
|
76
|
+
map.setConfig(config);</code></pre>`,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
cy.get(".code-view code")
|
|
81
|
+
.should("contain.text", 'import "@eox/map"')
|
|
82
|
+
.and("contain.text", "const config =")
|
|
83
|
+
.and("contain.text", "map.setConfig(config)");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article class="tutorial card border no-overflow">
|
|
3
|
+
<!-- Demo Section -->
|
|
4
|
+
<div>
|
|
5
|
+
<!-- Description Section -->
|
|
6
|
+
<div
|
|
7
|
+
v-if="$slots.default || $slots.description"
|
|
8
|
+
class="description padding border-bottom"
|
|
9
|
+
>
|
|
10
|
+
<slot name="description">
|
|
11
|
+
<slot></slot>
|
|
12
|
+
</slot>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<ClientOnly>
|
|
16
|
+
<div
|
|
17
|
+
class="demo-wrapper relative border-bottom"
|
|
18
|
+
:style="{ height: demoHeight }"
|
|
19
|
+
v-if="$slots.demo"
|
|
20
|
+
>
|
|
21
|
+
<slot name="demo"></slot>
|
|
22
|
+
</div>
|
|
23
|
+
</ClientOnly>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div v-if="$slots.controls" class="padding">
|
|
27
|
+
<slot name="controls"></slot>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Code Section -->
|
|
31
|
+
<div class="code-view top-margin">
|
|
32
|
+
<div v-show="!collapsible || isCodeExpanded">
|
|
33
|
+
<slot name="code"></slot>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</article>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<script setup>
|
|
40
|
+
/**
|
|
41
|
+
* Tutorial component for displaying interactive demos with code examples.
|
|
42
|
+
*
|
|
43
|
+
* @component
|
|
44
|
+
* @example
|
|
45
|
+
* <Tutorial demoHeight="600px">
|
|
46
|
+
* <p>This tutorial shows how to use the map component.</p>
|
|
47
|
+
*
|
|
48
|
+
* <template #demo>
|
|
49
|
+
* <eox-map></eox-map>
|
|
50
|
+
* </template>
|
|
51
|
+
*
|
|
52
|
+
* <template #code>
|
|
53
|
+
*
|
|
54
|
+
* ::: code-group
|
|
55
|
+
* ```vue
|
|
56
|
+
* <template>
|
|
57
|
+
* first tab content
|
|
58
|
+
* </template>
|
|
59
|
+
* ```
|
|
60
|
+
* ```html
|
|
61
|
+
* <html>
|
|
62
|
+
* auto highlighted code content
|
|
63
|
+
* </html>
|
|
64
|
+
* ```
|
|
65
|
+
* ```js
|
|
66
|
+
* console.log("third tab content");
|
|
67
|
+
* ```
|
|
68
|
+
* :::
|
|
69
|
+
* </template>
|
|
70
|
+
|
|
71
|
+
* <template #controls>
|
|
72
|
+
* <button>Reset Map</button>
|
|
73
|
+
* </template>
|
|
74
|
+
* </Tutorial>
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
defineProps({
|
|
78
|
+
demoHeight: {
|
|
79
|
+
type: String,
|
|
80
|
+
default: "500px",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<style scoped>
|
|
86
|
+
.demo-wrapper {
|
|
87
|
+
overflow: hidden;
|
|
88
|
+
transition: height 0.3s ease;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Code and pre styling - VitePress compatibility */
|
|
92
|
+
:deep(code *),
|
|
93
|
+
:deep(pre *),
|
|
94
|
+
:deep(.vp-code *),
|
|
95
|
+
:deep(.vp-code-group *:not(.tabs *)),
|
|
96
|
+
:deep(.url-display),
|
|
97
|
+
:deep(kbd),
|
|
98
|
+
:deep(samp),
|
|
99
|
+
:deep(var),
|
|
100
|
+
:deep(.monospace) {
|
|
101
|
+
font-family: "SFMono-Regular", "Menlo", "Monaco", "Consolas",
|
|
102
|
+
"Liberation Mono", "Ubuntu Mono", "Courier New", monospace !important;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Inline code */
|
|
106
|
+
:deep(code) {
|
|
107
|
+
background: #f6f8fa;
|
|
108
|
+
color: #24292e;
|
|
109
|
+
padding: 2px 4px;
|
|
110
|
+
border-radius: 16px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Code blocks */
|
|
114
|
+
:deep(pre) {
|
|
115
|
+
background: #f6f8fa;
|
|
116
|
+
color: #24292e;
|
|
117
|
+
padding: 16px;
|
|
118
|
+
border-radius: 4px;
|
|
119
|
+
overflow-x: auto;
|
|
120
|
+
line-height: 1.4;
|
|
121
|
+
margin: 16px 0;
|
|
122
|
+
border: 1px solid #e1e4e8;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* VitePress code groups */
|
|
126
|
+
:deep(.vp-code-group .tabs) {
|
|
127
|
+
padding: 0;
|
|
128
|
+
margin-left: 0;
|
|
129
|
+
margin-right: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
:deep(.vp-code-group .tabs label) {
|
|
133
|
+
flex: 1;
|
|
134
|
+
text-align: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
:deep(.vp-code-group .tabs input:checked + label) {
|
|
138
|
+
color: var(--primary);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
:deep(.vp-code-group .tabs label:hover) {
|
|
142
|
+
background: var(--surface-container-high);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
:deep(.vp-code-group .tabs label::after) {
|
|
146
|
+
left: 0;
|
|
147
|
+
right: 0;
|
|
148
|
+
height: 1px;
|
|
149
|
+
}
|
|
150
|
+
:deep(.vp-code-group input:checked + label::after) {
|
|
151
|
+
height: 2px;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
:deep(.vp-code-group .blocks) {
|
|
155
|
+
margin-top: 0.5rem;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
:deep(.code-view div[class*="language-"]) {
|
|
159
|
+
margin-left: 0;
|
|
160
|
+
margin-right: 0;
|
|
161
|
+
}
|
|
162
|
+
</style>
|
package/src/helpers.js
CHANGED
|
@@ -173,3 +173,28 @@ export const isActive = (item, path) => {
|
|
|
173
173
|
|
|
174
174
|
return false;
|
|
175
175
|
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get the translation for a given key from an i18n object.
|
|
179
|
+
* Falls back to the key if no translation is found.
|
|
180
|
+
* Supports dot notation for nested objects.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} key - The key to translate
|
|
183
|
+
* @param {object} i18n - The translation table
|
|
184
|
+
* @returns {string} The translated string or the key itself
|
|
185
|
+
*/
|
|
186
|
+
export const t = (key, i18n) => {
|
|
187
|
+
if (!key) return "";
|
|
188
|
+
if (!i18n) return key;
|
|
189
|
+
|
|
190
|
+
let obj = i18n;
|
|
191
|
+
const parts = key.split(".");
|
|
192
|
+
for (const part of parts) {
|
|
193
|
+
if (obj && typeof obj === "object" && part in obj) {
|
|
194
|
+
obj = obj[part];
|
|
195
|
+
} else {
|
|
196
|
+
return key;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return typeof obj === "string" ? obj : key;
|
|
200
|
+
};
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import CookieBanner from "./components/CookieBanner.vue";
|
|
|
12
12
|
import CookieSettings from "./components/CookieSettings.vue";
|
|
13
13
|
import NotFound from "./components/NotFound.vue";
|
|
14
14
|
import DataTable from "./components/DataTable.vue";
|
|
15
|
+
import Tutorial from "./components/Tutorial.vue";
|
|
15
16
|
import Layout from "./Layout.vue";
|
|
16
17
|
import "./style.css";
|
|
17
18
|
|
|
@@ -32,6 +33,7 @@ export default {
|
|
|
32
33
|
app.component("CookieSettings", CookieSettings);
|
|
33
34
|
app.component("NotFound", NotFound);
|
|
34
35
|
app.component("DataTable", DataTable);
|
|
36
|
+
app.component("Tutorial", Tutorial);
|
|
35
37
|
|
|
36
38
|
router.onAfterRouteChanged = () => {
|
|
37
39
|
if (!import.meta.env.SSR) {
|