@code-coaching/vuetiful 0.19.0 → 0.21.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/README.md +1 -1
- package/dist/style.css +1 -1
- package/dist/styles/all.css +232 -0
- package/dist/types/components/molecules/VAlert.test.d.ts +1 -0
- package/dist/types/components/molecules/VAlert.vue.d.ts +44 -0
- package/dist/types/components/molecules/VCard/VCard.test.d.ts +1 -0
- package/dist/types/components/molecules/VCard/VCard.vue.d.ts +52 -0
- package/dist/types/components/molecules/VCard/VCardBody.test.d.ts +1 -0
- package/dist/types/components/molecules/VCard/VCardBody.vue.d.ts +2 -0
- package/dist/types/components/molecules/VCard/VCardFooter.test.d.ts +1 -0
- package/dist/types/components/molecules/VCard/VCardFooter.vue.d.ts +2 -0
- package/dist/types/components/molecules/VCard/VCardHeader.test.d.ts +1 -0
- package/dist/types/components/molecules/VCard/VCardHeader.vue.d.ts +2 -0
- package/dist/types/components/molecules/index.d.ts +6 -1
- package/dist/vuetiful.es.mjs +398 -139
- package/dist/vuetiful.umd.js +10 -10
- package/package.json +1 -1
- package/src/components/molecules/VAlert.test.ts +90 -0
- package/src/components/molecules/VAlert.vue +133 -0
- package/src/components/molecules/VCard/VCard.test.ts +47 -0
- package/src/components/molecules/VCard/VCard.vue +71 -0
- package/src/components/molecules/VCard/VCardBody.test.ts +21 -0
- package/src/components/molecules/VCard/VCardBody.vue +5 -0
- package/src/components/molecules/VCard/VCardFooter.test.ts +45 -0
- package/src/components/molecules/VCard/VCardFooter.vue +11 -0
- package/src/components/molecules/VCard/VCardHeader.test.ts +68 -0
- package/src/components/molecules/VCard/VCardHeader.vue +33 -0
- package/src/components/molecules/VListbox/VListbox.vue +3 -1
- package/src/components/molecules/index.ts +12 -0
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VAlert } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VAlert", () => {
|
|
6
|
+
test("types", () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-alert data-test="default"></v-alert>
|
|
10
|
+
<v-alert data-test="info" type="info"></v-alert>
|
|
11
|
+
<v-alert data-test="success" type="success"></v-alert>
|
|
12
|
+
<v-alert data-test="warning" type="warning"></v-alert>
|
|
13
|
+
<v-alert data-test="error" type="error"></v-alert>
|
|
14
|
+
`,
|
|
15
|
+
components: {
|
|
16
|
+
"v-alert": VAlert,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const defaultAlert = wrapper.find("[data-test=default]");
|
|
21
|
+
expect(defaultAlert.classes()).toContain("variant-filled-primary");
|
|
22
|
+
|
|
23
|
+
const infoAlert = wrapper.find("[data-test=info]");
|
|
24
|
+
expect(infoAlert.classes()).toContain("variant-filled");
|
|
25
|
+
|
|
26
|
+
const successAlert = wrapper.find("[data-test=success]");
|
|
27
|
+
expect(successAlert.classes()).toContain("variant-filled-success");
|
|
28
|
+
|
|
29
|
+
const warningAlert = wrapper.find("[data-test=warning]");
|
|
30
|
+
expect(warningAlert.classes()).toContain("variant-filled-warning");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("given close icon is clicked", () => {
|
|
34
|
+
test("should emit close", async () => {
|
|
35
|
+
const wrapper = mount(VAlert);
|
|
36
|
+
await wrapper.find("[data-test=close]").trigger("click");
|
|
37
|
+
expect(wrapper.emitted()["close"][0]).toEqual([]);
|
|
38
|
+
|
|
39
|
+
await wrapper.find("[data-test=close]").trigger("keydown", { key: "Enter" });
|
|
40
|
+
expect(wrapper.emitted()["close"][0]).toEqual([]);
|
|
41
|
+
|
|
42
|
+
await wrapper.find("[data-test=close]").trigger("keydown", { key: " " });
|
|
43
|
+
expect(wrapper.emitted()["close"][0]).toEqual([]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("given a pre slot is provided", () => {
|
|
48
|
+
test("should render the pre slot", () => {
|
|
49
|
+
const wrapper = mount(VAlert, {
|
|
50
|
+
slots: {
|
|
51
|
+
pre: "John Duck",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
expect(wrapper.text()).toContain("John Duck");
|
|
55
|
+
});
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe("given a actions slot is provided", () => {
|
|
59
|
+
test("should render the actions slot", () => {
|
|
60
|
+
const wrapper = mount(VAlert, {
|
|
61
|
+
slots: {
|
|
62
|
+
actions: "John Duck",
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
expect(wrapper.text()).toContain("John Duck");
|
|
66
|
+
});
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
describe("given hide-icon prop is present", () => {
|
|
70
|
+
test("should not render an icon", () => {
|
|
71
|
+
const wrapper = mount(VAlert, {
|
|
72
|
+
props: {
|
|
73
|
+
hideIcon: true,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
expect(wrapper.findAll(".icon").length).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe("given hide-close prop is present", () => {
|
|
81
|
+
test("should not render a close icon", () => {
|
|
82
|
+
const wrapper = mount(VAlert, {
|
|
83
|
+
props: {
|
|
84
|
+
hideClose: true,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
expect(wrapper.find("[data-test=close]").exists()).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { PropType, computed } from "vue";
|
|
3
|
+
|
|
4
|
+
const emit = defineEmits(["close"]);
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
hideIcon: {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
default: false,
|
|
9
|
+
},
|
|
10
|
+
hideClose: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
default: false,
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
show: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: true,
|
|
18
|
+
},
|
|
19
|
+
type: {
|
|
20
|
+
type: String as PropType<"info" | "success" | "warning" | "error">,
|
|
21
|
+
default: "",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const typeBackground = computed(() => {
|
|
26
|
+
switch (props.type) {
|
|
27
|
+
case "info":
|
|
28
|
+
return "variant-filled";
|
|
29
|
+
case "success":
|
|
30
|
+
return "variant-filled-success";
|
|
31
|
+
case "warning":
|
|
32
|
+
return "variant-filled-warning";
|
|
33
|
+
case "error":
|
|
34
|
+
return "variant-filled-error";
|
|
35
|
+
default:
|
|
36
|
+
return "variant-filled-primary";
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const close = () => emit("close");
|
|
41
|
+
const handleKeydown = (event: KeyboardEvent) => {
|
|
42
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
43
|
+
close();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<aside
|
|
50
|
+
v-if="show"
|
|
51
|
+
:class="`vuetiful-alert flex w-full flex-row items-center gap-4 p-4 border-token rounded-container-token ${typeBackground}`"
|
|
52
|
+
>
|
|
53
|
+
<div v-if="!hideIcon">
|
|
54
|
+
<slot v-if="$slots.pre" name="pre" />
|
|
55
|
+
<template v-if="!$slots.pre">
|
|
56
|
+
<!-- https://fontawesome.com/icons/circle-info?f=classic&s=solid -->
|
|
57
|
+
<svg
|
|
58
|
+
v-if="type === 'info'"
|
|
59
|
+
class="icon"
|
|
60
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
61
|
+
viewBox="0 0 512 512"
|
|
62
|
+
>
|
|
63
|
+
<path
|
|
64
|
+
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
|
|
65
|
+
/>
|
|
66
|
+
</svg>
|
|
67
|
+
|
|
68
|
+
<!-- https://fontawesome.com/icons/circle-check?f=classic&s=solid -->
|
|
69
|
+
<svg
|
|
70
|
+
v-if="type === 'success'"
|
|
71
|
+
class="icon"
|
|
72
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
73
|
+
viewBox="0 0 512 512"
|
|
74
|
+
>
|
|
75
|
+
<path
|
|
76
|
+
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
|
|
77
|
+
/>
|
|
78
|
+
</svg>
|
|
79
|
+
|
|
80
|
+
<!-- https://fontawesome.com/icons/circle-exclamation?f=classic&s=solid -->
|
|
81
|
+
<svg
|
|
82
|
+
v-if="type === 'warning'"
|
|
83
|
+
class="icon"
|
|
84
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
85
|
+
viewBox="0 0 512 512"
|
|
86
|
+
>
|
|
87
|
+
<path
|
|
88
|
+
d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
|
|
89
|
+
/>
|
|
90
|
+
</svg>
|
|
91
|
+
|
|
92
|
+
<!-- https://fontawesome.com/icons/triangle-exclamation?f=classic&s=solid -->
|
|
93
|
+
<svg
|
|
94
|
+
v-if="type === 'error'"
|
|
95
|
+
class="icon"
|
|
96
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
97
|
+
viewBox="0 0 512 512"
|
|
98
|
+
>
|
|
99
|
+
<path
|
|
100
|
+
d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
|
|
101
|
+
/>
|
|
102
|
+
</svg>
|
|
103
|
+
</template>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="vuetiful-alert-message flex-auto">
|
|
107
|
+
<slot />
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<slot name="actions"> </slot>
|
|
111
|
+
<!-- https://fontawesome.com/icons/xmark?f=classic&s=solid -->
|
|
112
|
+
<svg
|
|
113
|
+
data-test="close"
|
|
114
|
+
v-if="!hideClose"
|
|
115
|
+
tabindex="0"
|
|
116
|
+
@keydown="handleKeydown"
|
|
117
|
+
@click="close"
|
|
118
|
+
class="icon hover:cursor-pointer"
|
|
119
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
+
viewBox="0 0 384 512"
|
|
121
|
+
>
|
|
122
|
+
<path
|
|
123
|
+
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
|
|
124
|
+
/>
|
|
125
|
+
</svg>
|
|
126
|
+
</aside>
|
|
127
|
+
</template>
|
|
128
|
+
|
|
129
|
+
<style scoped>
|
|
130
|
+
.icon {
|
|
131
|
+
@apply my-1 h-6 min-h-[1.5rem] w-6 min-w-[1.5rem] fill-current;
|
|
132
|
+
}
|
|
133
|
+
</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VCard } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VCard", () => {
|
|
6
|
+
test("defaults", async () => {
|
|
7
|
+
const wrapper = mount(VCard);
|
|
8
|
+
expect(wrapper.props()).toEqual({
|
|
9
|
+
background: "bg-surface-200-700-token",
|
|
10
|
+
clickable: false,
|
|
11
|
+
hideSeparator: false,
|
|
12
|
+
horizontal: false,
|
|
13
|
+
text: "text-surface-900-50-token",
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("given the card is clicked", () => {
|
|
18
|
+
describe("given the card is not clickable", () => {
|
|
19
|
+
test("should not emit click", async () => {
|
|
20
|
+
const wrapper = mount(VCard, { props: { clickable: false } });
|
|
21
|
+
expect(wrapper.attributes()['tabindex']).toBeUndefined();
|
|
22
|
+
await wrapper.trigger("click");
|
|
23
|
+
expect(wrapper.emitted()["click"]).toBeUndefined();
|
|
24
|
+
|
|
25
|
+
await wrapper.trigger("keydown", { key: "Enter" });
|
|
26
|
+
expect(wrapper.emitted()["click"]).toBeUndefined();
|
|
27
|
+
|
|
28
|
+
await wrapper.trigger("keydown", { key: " " });
|
|
29
|
+
expect(wrapper.emitted()["click"]).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe("given the card is clickable", () => {
|
|
33
|
+
test("should emit click", async () => {
|
|
34
|
+
const wrapper = mount(VCard, { props: { clickable: true } });
|
|
35
|
+
expect(wrapper.attributes()['tabindex']).toEqual("0");
|
|
36
|
+
await wrapper.trigger("click");
|
|
37
|
+
expect(wrapper.emitted()["click"].length).toEqual(1);
|
|
38
|
+
|
|
39
|
+
await wrapper.trigger("keydown", { key: "Enter" });
|
|
40
|
+
expect(wrapper.emitted()["click"].length).toEqual(2);
|
|
41
|
+
|
|
42
|
+
await wrapper.trigger("keydown", { key: " " });
|
|
43
|
+
expect(wrapper.emitted()["click"].length).toEqual(3);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { provide } from "vue";
|
|
3
|
+
|
|
4
|
+
const emit = defineEmits(["click"]);
|
|
5
|
+
|
|
6
|
+
const props = defineProps({
|
|
7
|
+
hideSeparator: {
|
|
8
|
+
type: Boolean,
|
|
9
|
+
default: false,
|
|
10
|
+
},
|
|
11
|
+
background: {
|
|
12
|
+
type: String,
|
|
13
|
+
default: "bg-surface-200-700-token",
|
|
14
|
+
},
|
|
15
|
+
text: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: "text-surface-900-50-token",
|
|
18
|
+
},
|
|
19
|
+
horizontal: {
|
|
20
|
+
type: Boolean,
|
|
21
|
+
default: false,
|
|
22
|
+
},
|
|
23
|
+
clickable: {
|
|
24
|
+
type: Boolean,
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
provide("hideSeparator", props.hideSeparator);
|
|
30
|
+
|
|
31
|
+
const onClick = () => {
|
|
32
|
+
if (!props.clickable) return;
|
|
33
|
+
emit("click");
|
|
34
|
+
};
|
|
35
|
+
const onKeydown = (event: KeyboardEvent) => {
|
|
36
|
+
if (!props.clickable) return;
|
|
37
|
+
if (event.key === "Enter") {
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
emit("click");
|
|
40
|
+
}
|
|
41
|
+
if (event.key === " ") {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
emit("click");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<template>
|
|
49
|
+
<div
|
|
50
|
+
@click="onClick"
|
|
51
|
+
@keydown="onKeydown"
|
|
52
|
+
:tabindex="clickable ? 0 : undefined"
|
|
53
|
+
:class="`vuetiful-card flex border-token rounded-container-token ring-outline-token ${
|
|
54
|
+
horizontal ? 'flex-row' : 'flex-col'
|
|
55
|
+
} ${background} ${text} ${clickable ? 'card-hover hover:cursor-pointer' : ''}`"
|
|
56
|
+
>
|
|
57
|
+
<slot />
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.vuetiful-card-header {
|
|
63
|
+
border-top-left-radius: inherit;
|
|
64
|
+
border-top-right-radius: inherit;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.vuetiful-card-header > * {
|
|
68
|
+
border-top-left-radius: inherit;
|
|
69
|
+
border-top-right-radius: inherit;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VCard, VCardBody } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VCardBody", () => {
|
|
6
|
+
test("defaults", async () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-card>
|
|
10
|
+
<v-card-body>John Duck</v-card-body>
|
|
11
|
+
</v-card>
|
|
12
|
+
`,
|
|
13
|
+
components: {
|
|
14
|
+
"v-card": VCard,
|
|
15
|
+
"v-card-body": VCardBody,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(wrapper.text()).toEqual("John Duck");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VCard, VCardFooter } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VCardFooter", () => {
|
|
6
|
+
test("defaults", async () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-card>
|
|
10
|
+
<v-card-footer>John Duck</v-card-footer>
|
|
11
|
+
</v-card>
|
|
12
|
+
`,
|
|
13
|
+
components: {
|
|
14
|
+
"v-card": VCard,
|
|
15
|
+
"v-card-footer": VCardFooter,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const separator = wrapper.find("[data-test='vuetiful-card-footer-separator']");
|
|
20
|
+
const content = wrapper.find("[data-test='vuetiful-card-footer-content']");
|
|
21
|
+
expect(separator.element.tagName).toEqual("HR");
|
|
22
|
+
expect(content.text()).toEqual("John Duck");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("given hideSeparator is true", () => {
|
|
26
|
+
test("should not show separator", async () => {
|
|
27
|
+
const wrapper = mount({
|
|
28
|
+
template: /*html*/ `
|
|
29
|
+
<v-card hide-separator>
|
|
30
|
+
<v-card-footer>John Duck</v-card-footer>
|
|
31
|
+
</v-card>
|
|
32
|
+
`,
|
|
33
|
+
components: {
|
|
34
|
+
"v-card": VCard,
|
|
35
|
+
"v-card-footer": VCardFooter,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const separator = wrapper.find("[data-test='vuetiful-card-footer-separator']");
|
|
40
|
+
const content = wrapper.find("[data-test='vuetiful-card-footer-content']");
|
|
41
|
+
expect(separator.exists()).toEqual(false);
|
|
42
|
+
expect(content.text()).toEqual("John Duck");
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { inject } from "vue";
|
|
3
|
+
const hideSeparator = inject("hideSeparator", false);
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<hr v-if="!hideSeparator" data-test="vuetiful-card-footer-separator" class="opacity-90" />
|
|
8
|
+
<div data-test="vuetiful-card-footer-content" class="vuetiful-card-footer p-4">
|
|
9
|
+
<slot />
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VCard, VCardHeader } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VCardHeader", () => {
|
|
6
|
+
test("defaults", async () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-card>
|
|
10
|
+
<v-card-header>John Duck</v-card-header>
|
|
11
|
+
</v-card>
|
|
12
|
+
`,
|
|
13
|
+
components: {
|
|
14
|
+
"v-card": VCard,
|
|
15
|
+
"v-card-header": VCardHeader,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const separator = wrapper.find("[data-test='vuetiful-card-header-separator']");
|
|
20
|
+
const content = wrapper.find("[data-test='vuetiful-card-header-content']");
|
|
21
|
+
expect(separator.element.tagName).toEqual("HR");
|
|
22
|
+
expect(content.text()).toEqual("John Duck");
|
|
23
|
+
expect(content.classes()).toContain("p-4");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("given hideSeparator is true", () => {
|
|
27
|
+
test("should not show separator", async () => {
|
|
28
|
+
const wrapper = mount({
|
|
29
|
+
template: /*html*/ `
|
|
30
|
+
<v-card hide-separator>
|
|
31
|
+
<v-card-header>John Duck</v-card-header>
|
|
32
|
+
</v-card>
|
|
33
|
+
`,
|
|
34
|
+
components: {
|
|
35
|
+
"v-card": VCard,
|
|
36
|
+
"v-card-header": VCardHeader,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const separator = wrapper.find("[data-test='vuetiful-card-header-separator']");
|
|
41
|
+
const content = wrapper.find("[data-test='vuetiful-card-header-content']");
|
|
42
|
+
expect(separator.exists()).toEqual(false);
|
|
43
|
+
expect(content.text()).toEqual("John Duck");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("given an image is present", () => {
|
|
48
|
+
test("should not have padding", async () => {
|
|
49
|
+
const wrapper = mount({
|
|
50
|
+
template: /*html*/ `
|
|
51
|
+
<v-card>
|
|
52
|
+
<v-card-header>
|
|
53
|
+
<img src="fakeUrl" />
|
|
54
|
+
</v-card-header>
|
|
55
|
+
</v-card>
|
|
56
|
+
`,
|
|
57
|
+
components: {
|
|
58
|
+
"v-card": VCard,
|
|
59
|
+
"v-card-header": VCardHeader,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
await wrapper.vm.$nextTick();
|
|
63
|
+
|
|
64
|
+
const content = wrapper.find("[data-test='vuetiful-card-header-content']");
|
|
65
|
+
expect(content.classes()).not.toContain("p-4");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Ref, computed, inject, ref } from 'vue';
|
|
3
|
+
|
|
4
|
+
const headerRef = ref() as Ref<HTMLDivElement>;
|
|
5
|
+
|
|
6
|
+
const hasImageAsChild = computed(() => {
|
|
7
|
+
const children = headerRef.value?.children;
|
|
8
|
+
if (!children) return false;
|
|
9
|
+
const childrenArray = Array.from(children);
|
|
10
|
+
return childrenArray.some((child) => child.tagName === 'IMG');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const hideSeparator = inject('hideSeparator', false);
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<div ref="headerRef" data-test='vuetiful-card-header-content' :class="`vuetiful-card-header ${hasImageAsChild ? '' : 'p-4'}`">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
<hr v-if="!hideSeparator" data-test='vuetiful-card-header-separator' class="opacity-90" />
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
.vuetiful-card-header {
|
|
25
|
+
border-top-left-radius: inherit;
|
|
26
|
+
border-top-right-radius: inherit;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.vuetiful-card-header > * {
|
|
30
|
+
border-top-left-radius: inherit;
|
|
31
|
+
border-top-right-radius: inherit;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
@@ -101,7 +101,9 @@ const showText = computed(() => {
|
|
|
101
101
|
v-model="parentModelValue"
|
|
102
102
|
>
|
|
103
103
|
<v-listbox-label v-if="labelText" :class="labelClasses">{{ labelText }}</v-listbox-label>
|
|
104
|
-
<v-listbox-button data-test="v-listbox-button"
|
|
104
|
+
<v-listbox-button data-test="v-listbox-button" :class="`${background} ${text}`">
|
|
105
|
+
{{ showText }}
|
|
106
|
+
</v-listbox-button>
|
|
105
107
|
<!-- TODO: Add configurable transition -->
|
|
106
108
|
<transition
|
|
107
109
|
enter-active-class="transition duration-150 ease-in-out"
|
|
@@ -17,9 +17,21 @@ import VTabs from "./VTabs/VTabs.vue";
|
|
|
17
17
|
import VAccordion from "./VAccordion/VAccordion.vue";
|
|
18
18
|
import VAccordionItem from "./VAccordion/VAccordionItem.vue";
|
|
19
19
|
|
|
20
|
+
import VAlert from "./VAlert.vue";
|
|
21
|
+
|
|
22
|
+
import VCard from "./VCard/VCard.vue";
|
|
23
|
+
import VCardBody from "./VCard/VCardBody.vue";
|
|
24
|
+
import VCardFooter from "./VCard/VCardFooter.vue";
|
|
25
|
+
import VCardHeader from "./VCard/VCardHeader.vue";
|
|
26
|
+
|
|
20
27
|
export {
|
|
21
28
|
VAccordion,
|
|
22
29
|
VAccordionItem,
|
|
30
|
+
VAlert,
|
|
31
|
+
VCard,
|
|
32
|
+
VCardBody,
|
|
33
|
+
VCardFooter,
|
|
34
|
+
VCardHeader,
|
|
23
35
|
VDrawer,
|
|
24
36
|
VListbox,
|
|
25
37
|
VListboxButton,
|