@code-coaching/vuetiful 0.8.1 → 0.10.0
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/dist/style.css +1 -1
- package/dist/styles/all.css +387 -19
- package/dist/types/components/atoms/VBadge.vue.d.ts +1 -1
- package/dist/types/components/atoms/VButton.vue.d.ts +14 -7
- package/dist/types/components/atoms/VChip.vue.d.ts +1 -1
- package/dist/types/components/molecules/VShell.vue.d.ts +8 -0
- package/dist/types/services/drawer.service.test.d.ts +1 -0
- package/dist/types/services/rail.service.test.d.ts +1 -0
- package/dist/types/utils/code-block/code-block.vue.d.ts +13 -55
- package/dist/types/utils/dark-mode/dark-mode.vue.d.ts +1 -1
- package/dist/types/utils/theme/theme-switcher.vue.d.ts +1 -1
- package/dist/vuetiful.es.mjs +149 -172
- package/dist/vuetiful.umd.js +10 -10
- package/package.json +1 -1
- package/src/components/atoms/VBadge.vue +1 -6
- package/src/components/atoms/VButton.test.ts +109 -2
- package/src/components/atoms/VButton.vue +32 -5
- package/src/components/atoms/VChip.vue +1 -6
- package/src/components/molecules/VDrawer.vue +2 -6
- package/src/components/molecules/VRail.vue +2 -9
- package/src/components/molecules/VRailTile.vue +36 -25
- package/src/components/molecules/VShell.vue +1 -3
- package/src/services/rail.service.test.ts +5 -3
- package/src/utils/code-block/code-block.vue +25 -50
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { mount } from "@vue/test-utils";
|
|
2
|
-
import { expect, test } from "vitest";
|
|
2
|
+
import { describe, expect, test, vi } from "vitest";
|
|
3
3
|
import { VButton } from ".";
|
|
4
4
|
|
|
5
|
-
test("VButton
|
|
5
|
+
test("VButton", () => {
|
|
6
6
|
expect(VButton).toBeTruthy();
|
|
7
|
+
});
|
|
7
8
|
|
|
9
|
+
test("VButton using slot", () => {
|
|
8
10
|
const vButtonElement = mount(VButton, {
|
|
9
11
|
slots: {
|
|
10
12
|
default: "John Duck",
|
|
@@ -13,3 +15,108 @@ test("VButton using slot", async () => {
|
|
|
13
15
|
|
|
14
16
|
expect(vButtonElement.text()).toContain("John Duck");
|
|
15
17
|
});
|
|
18
|
+
|
|
19
|
+
describe("VButton props", () => {
|
|
20
|
+
describe("given icon is true", () => {
|
|
21
|
+
test("should have btn-icon class, not have btn class", () => {
|
|
22
|
+
const vButtonElement = mount(VButton, {
|
|
23
|
+
props: {
|
|
24
|
+
icon: true,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
expect(vButtonElement.classes()).toContain("btn-icon");
|
|
28
|
+
expect(vButtonElement.classes()).not.toContain("btn");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("given icon is false", () => {
|
|
32
|
+
test("should have btn class, not have btn-icon class", () => {
|
|
33
|
+
const vButtonElement = mount(VButton, {
|
|
34
|
+
props: {
|
|
35
|
+
icon: false,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
expect(vButtonElement.classes()).not.toContain("btn-icon");
|
|
39
|
+
expect(vButtonElement.classes()).toContain("btn");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("VButton events", () => {
|
|
45
|
+
describe("given click event", () => {
|
|
46
|
+
test("should preventDefault and emit click event", async () => {
|
|
47
|
+
const vButtonElement = mount(VButton);
|
|
48
|
+
const clickEvent = { preventDefault: () => {} };
|
|
49
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, "preventDefault");
|
|
50
|
+
vButtonElement.trigger("click", clickEvent);
|
|
51
|
+
await vButtonElement.vm.$nextTick();
|
|
52
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
53
|
+
expect(vButtonElement.emitted("click")).toBeTruthy();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("VButton a11y", () => {
|
|
59
|
+
describe("a11y role", () => {
|
|
60
|
+
test("should have role button", () => {
|
|
61
|
+
const vButtonElement = mount(VButton);
|
|
62
|
+
expect(vButtonElement.attributes("role")).toBe("button");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe("a11y tabindex", () => {
|
|
66
|
+
test("should have tabindex 0", () => {
|
|
67
|
+
const vButtonElement = mount(VButton);
|
|
68
|
+
expect(vButtonElement.attributes("tabindex")).toBe("0");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("given keydown event", () => {
|
|
72
|
+
describe("given key is Enter", () => {
|
|
73
|
+
test("should preventDefault and emit click event", async () => {
|
|
74
|
+
const vButtonElement = mount(VButton);
|
|
75
|
+
const keydownEvent = { key: "Enter", preventDefault: () => {} };
|
|
76
|
+
const preventDefaultSpy = vi.spyOn(keydownEvent, "preventDefault");
|
|
77
|
+
vButtonElement.trigger("keydown", keydownEvent);
|
|
78
|
+
await vButtonElement.vm.$nextTick();
|
|
79
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
80
|
+
expect(vButtonElement.emitted("click")).toBeTruthy();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("given key is Space", () => {
|
|
85
|
+
test("should preventDefault and not emit click event", async () => {
|
|
86
|
+
const vButtonElement = mount(VButton);
|
|
87
|
+
const keydownEvent = { key: " ", preventDefault: () => {} };
|
|
88
|
+
const preventDefaultSpy = vi.spyOn(keydownEvent, "preventDefault");
|
|
89
|
+
vButtonElement.trigger("keydown", keydownEvent);
|
|
90
|
+
await vButtonElement.vm.$nextTick();
|
|
91
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
92
|
+
expect(vButtonElement.emitted("click")).toBeFalsy();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("given key is not Enter or Space", () => {
|
|
97
|
+
test("should not preventDefault and not emit click event", async () => {
|
|
98
|
+
const vButtonElement = mount(VButton);
|
|
99
|
+
const keydownEvent = { key: "a", preventDefault: () => {} };
|
|
100
|
+
const preventDefaultSpy = vi.spyOn(keydownEvent, "preventDefault");
|
|
101
|
+
vButtonElement.trigger("keydown", keydownEvent);
|
|
102
|
+
await vButtonElement.vm.$nextTick();
|
|
103
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled();
|
|
104
|
+
expect(vButtonElement.emitted("click")).toBeFalsy();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("given keyup event", () => {
|
|
110
|
+
describe("given key is Space", () => {
|
|
111
|
+
test("should preventDefault and emit click event", async () => {
|
|
112
|
+
const vButtonElement = mount(VButton);
|
|
113
|
+
const keyupEvent = { key: " ", preventDefault: () => {} };
|
|
114
|
+
const preventDefaultSpy = vi.spyOn(keyupEvent, "preventDefault");
|
|
115
|
+
vButtonElement.trigger("keyup", keyupEvent);
|
|
116
|
+
await vButtonElement.vm.$nextTick();
|
|
117
|
+
expect(preventDefaultSpy).toHaveBeenCalled();
|
|
118
|
+
expect(vButtonElement.emitted("click")).toBeTruthy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { useAttrs } from "vue";
|
|
3
|
-
|
|
4
2
|
defineProps({
|
|
3
|
+
icon: {
|
|
4
|
+
type: Boolean as () => boolean,
|
|
5
|
+
default: false,
|
|
6
|
+
},
|
|
5
7
|
tag: {
|
|
6
8
|
type: String as () => string,
|
|
7
9
|
default: "button",
|
|
8
10
|
},
|
|
9
11
|
});
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
+
const emit = defineEmits<{ (event: "click"): void }>();
|
|
13
|
+
|
|
14
|
+
const activate = () => {
|
|
15
|
+
emit("click");
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const clickHandler = (event: MouseEvent) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
activate();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const keydownHandler = (event: KeyboardEvent) => {
|
|
24
|
+
if (["Enter", " "].includes(event.key)) event.preventDefault();
|
|
25
|
+
if (event.key === "Enter") activate();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const keyupHandler = (event: KeyboardEvent) => {
|
|
29
|
+
if (event.key === " ") {
|
|
30
|
+
event.preventDefault();
|
|
31
|
+
activate();
|
|
32
|
+
}
|
|
33
|
+
};
|
|
12
34
|
</script>
|
|
13
35
|
|
|
14
36
|
<template>
|
|
15
37
|
<component
|
|
38
|
+
tabindex="0"
|
|
39
|
+
role="button"
|
|
16
40
|
:is="tag"
|
|
17
|
-
:class="`vuetiful-button btn border-token hover:cursor-pointer
|
|
41
|
+
:class="`vuetiful-button ${icon ? 'btn-icon' : 'btn'} border-token hover:cursor-pointer`"
|
|
42
|
+
@click="clickHandler"
|
|
43
|
+
@keydown="keydownHandler"
|
|
44
|
+
@keyup="keyupHandler"
|
|
18
45
|
>
|
|
19
46
|
<slot />
|
|
20
47
|
</component>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { CssClasses } from "@/index";
|
|
3
3
|
import { useDrawer } from "@/services";
|
|
4
|
-
import { Ref, computed, onMounted, ref, toRefs
|
|
4
|
+
import { Ref, computed, onMounted, ref, toRefs } from "vue";
|
|
5
5
|
|
|
6
6
|
const { drawer, close } = useDrawer();
|
|
7
|
-
const attrs = useAttrs();
|
|
8
7
|
|
|
9
8
|
// #region Props
|
|
10
9
|
const props = defineProps({
|
|
@@ -29,7 +28,6 @@ const props = defineProps({
|
|
|
29
28
|
},
|
|
30
29
|
});
|
|
31
30
|
|
|
32
|
-
// prettier-ignore
|
|
33
31
|
const { regionBackdrop, regionDrawer, labelledby, describedby } = toRefs(props);
|
|
34
32
|
// prettier-ignore
|
|
35
33
|
const presets = {
|
|
@@ -81,9 +79,7 @@ onMounted(() => {
|
|
|
81
79
|
<div
|
|
82
80
|
v-if="drawer.open"
|
|
83
81
|
ref="elemBackdrop"
|
|
84
|
-
:class="`drawer-backdrop backdrop-blur-xs fixed bottom-0 left-0 right-0 top-0 flex bg-surface-backdrop-token
|
|
85
|
-
attrs.class ?? ''
|
|
86
|
-
}`"
|
|
82
|
+
:class="`drawer-backdrop backdrop-blur-xs fixed bottom-0 left-0 right-0 top-0 flex bg-surface-backdrop-token z-40 ${regionBackdrop}`"
|
|
87
83
|
@mousedown="onBackdropInteraction"
|
|
88
84
|
@touchstart="onBackdropInteraction"
|
|
89
85
|
></div>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { CssClasses } from "@/index";
|
|
3
|
-
import { defineProps, provide
|
|
3
|
+
import { defineProps, provide } from "vue";
|
|
4
4
|
|
|
5
5
|
const props = defineProps({
|
|
6
6
|
active: {
|
|
@@ -26,19 +26,12 @@ const props = defineProps({
|
|
|
26
26
|
},
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
const attrs = useAttrs();
|
|
30
|
-
|
|
31
29
|
provide("active", props.active);
|
|
32
30
|
provide("hover", props.hover);
|
|
33
31
|
</script>
|
|
34
32
|
|
|
35
33
|
<template>
|
|
36
|
-
<div
|
|
37
|
-
class="v-rail"
|
|
38
|
-
:class="`grid h-full w-[70px] grid-rows-[auto_1fr_auto] gap-0 overflow-y-auto sm:w-20 ${
|
|
39
|
-
attrs.class || ''
|
|
40
|
-
}`"
|
|
41
|
-
>
|
|
34
|
+
<div class="v-rail grid h-full w-[70px] grid-rows-[auto_1fr_auto] gap-0 overflow-y-auto sm:w-20">
|
|
42
35
|
<div class="v-bar-lead" :class="regionLead"><slot name="lead" /></div>
|
|
43
36
|
<div class="v-bar-default" :class="regionDefault"><slot /></div>
|
|
44
37
|
<div class="v-bar-trail" :class="regionTrail"><slot name="trail" /></div>
|
|
@@ -37,36 +37,47 @@ const { selectedRailTile } = useRail();
|
|
|
37
37
|
const active = inject("active");
|
|
38
38
|
const hover = inject("hover");
|
|
39
39
|
|
|
40
|
-
const
|
|
41
|
-
if (!props.value) return;
|
|
40
|
+
const activate = () => {
|
|
42
41
|
selectedRailTile.value = props.value;
|
|
43
42
|
emit("click");
|
|
44
43
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
|
|
45
|
+
const clickHandler = (event: MouseEvent) => {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
activate();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const keydownHandler = (event: KeyboardEvent) => {
|
|
51
|
+
if (["Enter", " "].includes(event.key)) event.preventDefault();
|
|
52
|
+
if (event.key === "Enter") activate();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const keyupHandler = (event: KeyboardEvent) => {
|
|
56
|
+
if (event.key === " ") {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
activate();
|
|
59
|
+
}
|
|
50
60
|
};
|
|
51
61
|
</script>
|
|
52
62
|
|
|
53
63
|
<template>
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
<component
|
|
65
|
+
@click="clickHandler"
|
|
66
|
+
@keydown="keydownHandler"
|
|
67
|
+
@keyup="keyupHandler"
|
|
68
|
+
:is="tag"
|
|
69
|
+
v-bind="attrs"
|
|
70
|
+
:class="`app-rail-tile unstyled grid aspect-square w-full cursor-pointer place-content-center place-items-center space-y-1.5 ${hover} ${
|
|
71
|
+
selectedRailTile === value ? `${active}` : ''
|
|
72
|
+
}`"
|
|
73
|
+
>
|
|
74
|
+
<template v-if="$slots.default">
|
|
75
|
+
<div :class="`app-rail-tile-icon ${regionIcon}`"><slot /></div>
|
|
76
|
+
</template>
|
|
77
|
+
<template v-if="label">
|
|
78
|
+
<div :class="`app-rail-tile-label text-center text-xs font-bold ${regionLabel}`">
|
|
79
|
+
{{ label }}
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
82
|
+
</component>
|
|
72
83
|
</template>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* @slot pageFooter - Insert content that resides below your page content. Recommended for most layouts.
|
|
8
8
|
* @slot fixedFooter - Insert fixed footer content. Not recommended for most layouts.
|
|
9
9
|
*/
|
|
10
|
-
import { useAttrs } from "vue";
|
|
11
10
|
export type CssClasses = string;
|
|
12
11
|
defineProps({
|
|
13
12
|
regionPage: { type: String as () => CssClasses, default: "" },
|
|
@@ -19,11 +18,10 @@ defineProps({
|
|
|
19
18
|
slotPageFooter: { type: String as () => CssClasses, default: "" },
|
|
20
19
|
slotFixedFooter: { type: String as () => CssClasses, default: "" },
|
|
21
20
|
});
|
|
22
|
-
const attrs = useAttrs();
|
|
23
21
|
</script>
|
|
24
22
|
|
|
25
23
|
<template>
|
|
26
|
-
<div
|
|
24
|
+
<div class="vuetiful-shell flex h-full w-full flex-col overflow-hidden">
|
|
27
25
|
<header v-if="$slots.fixedHeader" :class="`vuetiful-fixed-header ${slotFixedHeader}`">
|
|
28
26
|
<slot name="fixedHeader" />
|
|
29
27
|
</header>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { describe, expect } from "vitest";
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { useRail } from "./rail.service";
|
|
3
3
|
|
|
4
4
|
const { selectedRailTile } = useRail();
|
|
5
5
|
|
|
6
6
|
describe("useRail", () => {
|
|
7
7
|
describe("selectedRailTile", () => {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
it("should expose selectedRailTile", () => {
|
|
9
|
+
selectedRailTile.value = "John Duck";
|
|
10
|
+
expect(selectedRailTile.value).toBe("John Duck");
|
|
11
|
+
});
|
|
10
12
|
});
|
|
11
13
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { CssClasses, vClipboard } from "@/index";
|
|
3
3
|
import "highlight.js/styles/github-dark.css";
|
|
4
|
-
import {
|
|
4
|
+
import { ref } from "vue";
|
|
5
5
|
import { useHighlight } from "./highlight.service";
|
|
6
6
|
|
|
7
7
|
const { highlight } = useHighlight();
|
|
@@ -15,54 +15,32 @@ const props = defineProps({
|
|
|
15
15
|
type: String,
|
|
16
16
|
default: "",
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
default: "bg-neutral-900/90",
|
|
21
|
-
},
|
|
22
|
-
blur: {
|
|
23
|
-
type: String as () => CssClasses,
|
|
24
|
-
default: "",
|
|
25
|
-
},
|
|
26
|
-
text: {
|
|
27
|
-
type: String as () => CssClasses,
|
|
28
|
-
default: "text-sm",
|
|
29
|
-
},
|
|
30
|
-
color: {
|
|
31
|
-
type: String as () => CssClasses,
|
|
32
|
-
default: "text-white",
|
|
33
|
-
},
|
|
34
|
-
rounded: {
|
|
18
|
+
|
|
19
|
+
headerClass: {
|
|
35
20
|
type: String as () => CssClasses,
|
|
36
|
-
default: "rounded-container-token",
|
|
37
21
|
},
|
|
38
|
-
|
|
22
|
+
preClass: {
|
|
39
23
|
type: String as () => CssClasses,
|
|
40
|
-
default: "shadow",
|
|
41
24
|
},
|
|
42
25
|
|
|
43
|
-
|
|
26
|
+
buttonClass: {
|
|
44
27
|
type: String as () => CssClasses,
|
|
45
28
|
default: "btn btn-sm variant-soft !text-white",
|
|
46
29
|
},
|
|
47
|
-
|
|
30
|
+
buttonText: {
|
|
48
31
|
type: String,
|
|
49
32
|
default: "Copy",
|
|
50
33
|
},
|
|
51
|
-
|
|
34
|
+
buttonCopiedText: {
|
|
52
35
|
type: String,
|
|
53
36
|
default: "👍",
|
|
54
37
|
},
|
|
55
38
|
});
|
|
56
39
|
|
|
57
|
-
const attrs = useAttrs();
|
|
58
40
|
const emit = defineEmits<{
|
|
59
41
|
(event: "copy"): void;
|
|
60
42
|
}>();
|
|
61
43
|
|
|
62
|
-
const cBase = "overflow-hidden shadow";
|
|
63
|
-
const cHeader = "text-xs text-white/50 uppercase flex justify-between items-center p-2 pl-4 pb-0";
|
|
64
|
-
const cPre = "whitespace-pre-wrap break-all p-4 pt-1";
|
|
65
|
-
|
|
66
44
|
const copyState = ref(false);
|
|
67
45
|
|
|
68
46
|
// Allow shorthand alias, but show full text in UI
|
|
@@ -80,27 +58,24 @@ function onCopyClick() {
|
|
|
80
58
|
}, 2000);
|
|
81
59
|
emit("copy");
|
|
82
60
|
}
|
|
83
|
-
|
|
84
|
-
// Reactive
|
|
85
|
-
const classesBase = computed(
|
|
86
|
-
() =>
|
|
87
|
-
`${cBase} ${props.background} ${props.blur} ${props.text} ${props.color} ${props.rounded} ${
|
|
88
|
-
props.shadow
|
|
89
|
-
} ${attrs.class ?? ""}`
|
|
90
|
-
);
|
|
91
61
|
</script>
|
|
92
62
|
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
63
|
+
<template v-if="language && code">
|
|
64
|
+
<div class="code-block bg-neutral-900/90 text-sm text-white shadow rounded-container-token">
|
|
65
|
+
<header
|
|
66
|
+
:class="`code-block-header flex items-center justify-between p-2 pb-0 pl-4 text-xs uppercase text-white/50 ${headerClass}`"
|
|
67
|
+
>
|
|
68
|
+
<span :class="`code-block-language`">{{ languageFormatter(language) }}</span>
|
|
69
|
+
<button
|
|
70
|
+
:class="`code-block-btn ${buttonClass}`"
|
|
71
|
+
@click="onCopyClick()"
|
|
72
|
+
v-clipboard="props.code"
|
|
73
|
+
>
|
|
74
|
+
{{ !copyState ? buttonText : buttonCopiedText }}
|
|
75
|
+
</button>
|
|
76
|
+
</header>
|
|
77
|
+
<pre
|
|
78
|
+
:class="`code-block-pre whitespace-pre-wrap break-all p-4 pt-1 ${preClass}`"
|
|
79
|
+
><code :class="`code-block-code language-${language}`" v-html="highlight(props.code, props.language)"></code></pre>
|
|
80
|
+
</div>
|
|
106
81
|
</template>
|