@code-coaching/vuetiful 0.16.6 → 0.18.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 +3 -4
- package/dist/style.css +1 -1
- package/dist/styles/all.css +173 -7
- package/dist/types/components/index.d.ts +2 -2
- package/dist/types/components/molecules/VListbox/VListbox.test.d.ts +1 -0
- package/dist/types/components/molecules/VListbox/VListbox.vue.d.ts +100 -0
- package/dist/types/components/molecules/VListbox/VListboxButton.test.d.ts +1 -0
- package/dist/types/components/molecules/VListbox/VListboxButton.vue.d.ts +23 -0
- package/dist/types/components/molecules/VListbox/VListboxItem.test.d.ts +1 -0
- package/dist/types/components/molecules/VListbox/VListboxItem.vue.d.ts +12 -0
- package/dist/types/components/molecules/VListbox/VListboxItems.test.d.ts +1 -0
- package/dist/types/components/molecules/VListbox/VListboxItems.vue.d.ts +23 -0
- package/dist/types/components/molecules/VListbox/VListboxLabel.test.d.ts +1 -0
- package/dist/types/components/molecules/VListbox/VListboxLabel.vue.d.ts +14 -0
- package/dist/types/components/molecules/VTabs/VTab.test.d.ts +1 -0
- package/dist/types/components/molecules/VTabs/VTab.vue.d.ts +14 -0
- package/dist/types/components/molecules/VTabs/VTabPanel.test.d.ts +1 -0
- package/dist/types/components/molecules/VTabs/VTabPanel.vue.d.ts +2 -0
- package/dist/types/components/molecules/VTabs/VTabs.test.d.ts +1 -0
- package/dist/types/components/molecules/VTabs/VTabs.vue.d.ts +86 -0
- package/dist/types/components/molecules/index.d.ts +10 -2
- package/dist/vuetiful.es.mjs +1333 -392
- package/dist/vuetiful.umd.js +12 -12
- package/package.json +1 -1
- package/src/components/atoms/VAvatar.vue +7 -1
- package/src/components/atoms/VBadge.test.ts +1 -1
- package/src/components/atoms/VChip.test.ts +1 -1
- package/src/components/atoms/VCodeBlock.test.ts +2 -2
- package/src/components/atoms/VLightSwitch.test.ts +2 -2
- package/src/components/atoms/VRadio/VRadioDescription.vue +3 -3
- package/src/components/atoms/VRadio/VRadioGroup.test.ts +4 -4
- package/src/components/atoms/VRadio/VRadioItem.test.ts +14 -14
- package/src/components/atoms/VSwitch/VSwitch.test.ts +4 -27
- package/src/components/atoms/VSwitch/VSwitchDescription.test.ts +1 -2
- package/src/components/atoms/VSwitch/VSwitchDescription.vue +2 -2
- package/src/components/atoms/VSwitch/VSwitchGroup.test.ts +2 -2
- package/src/components/atoms/VSwitch/VSwitchGroup.vue +2 -2
- package/src/components/atoms/VSwitch/VSwitchLabel.test.ts +3 -3
- package/src/components/atoms/VSwitch/VSwitchLabel.vue +3 -3
- package/src/components/index.ts +2 -2
- package/src/components/molecules/VDrawer.test.ts +2 -2
- package/src/components/molecules/VDrawer.vue +1 -1
- package/src/components/molecules/VListbox/VListbox.test.ts +119 -0
- package/src/components/molecules/VListbox/VListbox.vue +119 -0
- package/src/components/molecules/VListbox/VListboxButton.test.ts +57 -0
- package/src/components/molecules/VListbox/VListboxButton.vue +48 -0
- package/src/components/molecules/VListbox/VListboxItem.test.ts +51 -0
- package/src/components/molecules/VListbox/VListboxItem.vue +29 -0
- package/src/components/molecules/VListbox/VListboxItems.test.ts +44 -0
- package/src/components/molecules/VListbox/VListboxItems.vue +31 -0
- package/src/components/molecules/VListbox/VListboxLabel.test.ts +31 -0
- package/src/components/molecules/VListbox/VListboxLabel.vue +14 -0
- package/src/components/molecules/VPreview.test.ts +2 -2
- package/src/components/molecules/VRail.test.ts +2 -2
- package/src/components/molecules/VRailTile.test.ts +2 -2
- package/src/components/molecules/VShell.test.ts +2 -2
- package/src/components/molecules/VTabs/VTab.test.ts +122 -0
- package/src/components/molecules/VTabs/VTab.vue +39 -0
- package/src/components/molecules/VTabs/VTabPanel.test.ts +23 -0
- package/src/components/molecules/VTabs/VTabPanel.vue +9 -0
- package/src/components/molecules/VTabs/VTabs.test.ts +90 -0
- package/src/components/molecules/VTabs/VTabs.vue +84 -0
- package/src/components/molecules/index.ts +26 -2
- package/src/styles/elements/buttons.css +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ListboxButton } from "@headlessui/vue";
|
|
3
|
+
|
|
4
|
+
defineProps({
|
|
5
|
+
as: {
|
|
6
|
+
type: String,
|
|
7
|
+
default: "button",
|
|
8
|
+
},
|
|
9
|
+
hideIcon: {
|
|
10
|
+
type: Boolean,
|
|
11
|
+
default: false,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<ListboxButton
|
|
18
|
+
v-slot="{ open }"
|
|
19
|
+
:as="as"
|
|
20
|
+
class="variant-filled btn flex w-full justify-between active:scale-[100%]"
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
<span v-if="!hideIcon">
|
|
24
|
+
<slot v-if="!open" name="open-icon">
|
|
25
|
+
<!-- https://fontawesome.com/icons/chevron-down?f=classic&s=solid -->
|
|
26
|
+
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
27
|
+
<path
|
|
28
|
+
d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"
|
|
29
|
+
/>
|
|
30
|
+
</svg>
|
|
31
|
+
</slot>
|
|
32
|
+
<slot v-if="open" name="close-icon">
|
|
33
|
+
<!-- https://fontawesome.com/icons/chevron-up?f=classic&s=solid -->
|
|
34
|
+
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
|
35
|
+
<path
|
|
36
|
+
d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"
|
|
37
|
+
/>
|
|
38
|
+
</svg>
|
|
39
|
+
</slot>
|
|
40
|
+
</span>
|
|
41
|
+
</ListboxButton>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style scoped>
|
|
45
|
+
.icon {
|
|
46
|
+
@apply my-1 h-4 w-4 fill-current;
|
|
47
|
+
}
|
|
48
|
+
</style>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import VListbox from "./VListbox.vue";
|
|
5
|
+
import VListboxItem from "./VListboxItem.vue";
|
|
6
|
+
|
|
7
|
+
describe("VListboxItem props", () => {
|
|
8
|
+
test("defaults", async () => {
|
|
9
|
+
const listboxValue = ref("John");
|
|
10
|
+
const wrapper = mount({
|
|
11
|
+
setup() {
|
|
12
|
+
return { listboxValue };
|
|
13
|
+
},
|
|
14
|
+
template: /* html */ `
|
|
15
|
+
<v-listbox v-model="listboxValue">
|
|
16
|
+
<v-listbox-item value="John">John Duck</v-listbox-item>
|
|
17
|
+
<v-listbox-item value="Jane">Jane Duck</v-listbox-item>
|
|
18
|
+
</v-listbox>
|
|
19
|
+
`,
|
|
20
|
+
components: {
|
|
21
|
+
"v-listbox": VListbox,
|
|
22
|
+
"v-listbox-item": VListboxItem,
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
27
|
+
await listbox.find("button").trigger("click");
|
|
28
|
+
|
|
29
|
+
const listboxItems = listbox.findAll("[data-test='v-listbox-item']");
|
|
30
|
+
const selectedItem = listboxItems[0];
|
|
31
|
+
const normalItem = listboxItems[1];
|
|
32
|
+
expect(selectedItem.classes()).toEqual([
|
|
33
|
+
"vuetiful-listbox-item",
|
|
34
|
+
"px-4",
|
|
35
|
+
"py-1",
|
|
36
|
+
"text-base",
|
|
37
|
+
"rounded-token",
|
|
38
|
+
"variant-filled",
|
|
39
|
+
"cursor-pointer",
|
|
40
|
+
]);
|
|
41
|
+
expect(normalItem.classes()).toEqual([
|
|
42
|
+
"vuetiful-listbox-item",
|
|
43
|
+
"px-4",
|
|
44
|
+
"py-1",
|
|
45
|
+
"text-base",
|
|
46
|
+
"rounded-token",
|
|
47
|
+
"hover:variant-ghost",
|
|
48
|
+
"cursor-pointer",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ListboxOption } from "@headlessui/vue";
|
|
3
|
+
import { inject } from "vue";
|
|
4
|
+
|
|
5
|
+
defineProps({
|
|
6
|
+
value: {
|
|
7
|
+
type: [String, Number, Boolean, Object],
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const activeClass = inject("active") as string;
|
|
13
|
+
const hoverClass = inject("hover") as string;
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<ListboxOption v-slot="{ selected, disabled, active }" :value="value">
|
|
18
|
+
<div
|
|
19
|
+
data-test="v-listbox-item"
|
|
20
|
+
:class="`vuetiful-listbox-item px-4 py-1 text-base rounded-token ${
|
|
21
|
+
selected ? activeClass : `hover:${hoverClass}`
|
|
22
|
+
} ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'} ${
|
|
23
|
+
active && !selected ? hoverClass : ''
|
|
24
|
+
}`"
|
|
25
|
+
>
|
|
26
|
+
<slot />
|
|
27
|
+
</div>
|
|
28
|
+
</ListboxOption>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import VListbox from "./VListbox.vue";
|
|
4
|
+
import VListboxItem from "./VListboxItem.vue";
|
|
5
|
+
|
|
6
|
+
test("VListboxItems defaults", async () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-listbox>
|
|
10
|
+
<v-listbox-item value="John">John Duck</v-listbox-item>
|
|
11
|
+
</v-listbox>
|
|
12
|
+
`,
|
|
13
|
+
components: {
|
|
14
|
+
"v-listbox": VListbox,
|
|
15
|
+
"v-listbox-item": VListboxItem,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
20
|
+
await listbox.find("button").trigger("click");
|
|
21
|
+
|
|
22
|
+
const listboxItems = listbox.find("[data-test='listbox-items']");
|
|
23
|
+
expect(listboxItems.classes()).toContain("flex-col");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("VListboxItems horizontal", async () => {
|
|
27
|
+
const wrapper = mount({
|
|
28
|
+
template: /*html*/ `
|
|
29
|
+
<v-listbox horizontal>
|
|
30
|
+
<v-listbox-item value="John">John Duck</v-listbox-item>
|
|
31
|
+
</v-listbox>
|
|
32
|
+
`,
|
|
33
|
+
components: {
|
|
34
|
+
"v-listbox": VListbox,
|
|
35
|
+
"v-listbox-item": VListboxItem,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
40
|
+
await listbox.find("button").trigger("click");
|
|
41
|
+
|
|
42
|
+
const listboxItems = listbox.find("[data-test='listbox-items']");
|
|
43
|
+
expect(listboxItems.classes()).not.toContain("flex-col");
|
|
44
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ListboxOptions } from "@headlessui/vue";
|
|
3
|
+
import { inject } from "vue";
|
|
4
|
+
|
|
5
|
+
defineProps({
|
|
6
|
+
as: {
|
|
7
|
+
type: String,
|
|
8
|
+
default: "ul",
|
|
9
|
+
},
|
|
10
|
+
static: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
default: false,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const background = inject("background") as string;
|
|
17
|
+
const text = inject("text") as string;
|
|
18
|
+
const horizontal = inject("horizontal") as boolean;
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<ListboxOptions
|
|
23
|
+
:as="as"
|
|
24
|
+
:static="static"
|
|
25
|
+
:class="`p-4 border-token border-surface-400-500-token rounded-container-token ${background} ${text}`"
|
|
26
|
+
>
|
|
27
|
+
<div data-test="listbox-items" :class="`flex ${horizontal ? 'flex' : 'flex-col'} gap-1`">
|
|
28
|
+
<slot />
|
|
29
|
+
</div>
|
|
30
|
+
</ListboxOptions>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { expect, test } from "vitest";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import VListbox from "./VListbox.vue";
|
|
5
|
+
|
|
6
|
+
test("VListboxLabel using slot", () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: `
|
|
9
|
+
<v-listbox></v-listbox>
|
|
10
|
+
`,
|
|
11
|
+
components: {
|
|
12
|
+
"v-listbox": VListbox,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
expect(wrapper.text()).toBe("Select an option");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("VListboxLabel custom label", () => {
|
|
20
|
+
const wrapper = mount({
|
|
21
|
+
template: `
|
|
22
|
+
<v-listbox label-text="John Duck"></v-listbox>
|
|
23
|
+
`,
|
|
24
|
+
components: {
|
|
25
|
+
"v-listbox": VListbox,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const label = wrapper.find("label");
|
|
30
|
+
expect(label.text()).toBe("John Duck");
|
|
31
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VTab, VTabs } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VTab", () => {
|
|
6
|
+
test("defaults", async () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-tabs>
|
|
10
|
+
<template v-slot="tabs">
|
|
11
|
+
<v-tab data-test="vuetiful">Vuetiful</v-tab>
|
|
12
|
+
</template>
|
|
13
|
+
</v-tabs>
|
|
14
|
+
`,
|
|
15
|
+
components: {
|
|
16
|
+
"v-tabs": VTabs,
|
|
17
|
+
"v-tab": VTab,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const vuetiful = wrapper.find("[data-test='vuetiful']");
|
|
22
|
+
const slotContainer = vuetiful.find("[data-test='slot-container']");
|
|
23
|
+
expect(vuetiful.classes()).toEqual(["flex", "flex-col"]);
|
|
24
|
+
expect(slotContainer.classes()).toEqual([
|
|
25
|
+
"text-base",
|
|
26
|
+
"rounded-token",
|
|
27
|
+
"w-full",
|
|
28
|
+
"px-4",
|
|
29
|
+
"py-2",
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("vertical", async () => {
|
|
34
|
+
const wrapper = mount({
|
|
35
|
+
template: /*html*/ `
|
|
36
|
+
<v-tabs vertical>
|
|
37
|
+
<template v-slot="tabs">
|
|
38
|
+
<v-tab data-test="vuetiful">Vuetiful</v-tab>
|
|
39
|
+
</template>
|
|
40
|
+
</v-tabs>
|
|
41
|
+
`,
|
|
42
|
+
components: {
|
|
43
|
+
"v-tabs": VTabs,
|
|
44
|
+
"v-tab": VTab,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const vuetiful = wrapper.find("[data-test='vuetiful']");
|
|
49
|
+
expect(vuetiful.classes()).toEqual(["flex", "flex-row", "justify-between"]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("class-tab", async () => {
|
|
53
|
+
const wrapper = mount({
|
|
54
|
+
template: /*html*/ `
|
|
55
|
+
<v-tabs class-tab="my-custom-class">
|
|
56
|
+
<template v-slot="tabs">
|
|
57
|
+
<v-tab data-test="vuetiful">Vuetiful</v-tab>
|
|
58
|
+
</template>
|
|
59
|
+
</v-tabs>
|
|
60
|
+
`,
|
|
61
|
+
components: {
|
|
62
|
+
"v-tabs": VTabs,
|
|
63
|
+
"v-tab": VTab,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const vuetiful = wrapper.find("[data-test='vuetiful']");
|
|
68
|
+
const slotContainer = vuetiful.find("[data-test='slot-container']");
|
|
69
|
+
expect(slotContainer.classes()).toEqual(["text-base", "rounded-token", "my-custom-class"]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("hover/active", async () => {
|
|
73
|
+
const wrapper = mount({
|
|
74
|
+
template: /*html*/ `
|
|
75
|
+
<v-tabs active="my-custom-active-class">
|
|
76
|
+
<template v-slot="tabs">
|
|
77
|
+
<v-tab data-test="vuetiful">Vuetiful</v-tab>
|
|
78
|
+
<v-tab data-test="is">Is</v-tab>
|
|
79
|
+
<v-tab data-test="beautiful">Beautiful</v-tab>
|
|
80
|
+
</template>
|
|
81
|
+
</v-tabs>
|
|
82
|
+
`,
|
|
83
|
+
components: {
|
|
84
|
+
"v-tabs": VTabs,
|
|
85
|
+
"v-tab": VTab,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const vuetifulSlotContainer = wrapper
|
|
90
|
+
.find("[data-test='vuetiful']")
|
|
91
|
+
.find("[data-test='slot-container']");
|
|
92
|
+
const isSlotContainer = wrapper.find("[data-test='is']").find("[data-test='slot-container']");
|
|
93
|
+
const beautifulSlotContainer = wrapper
|
|
94
|
+
.find("[data-test='beautiful']")
|
|
95
|
+
.find("[data-test='slot-container']");
|
|
96
|
+
|
|
97
|
+
expect(vuetifulSlotContainer.classes()).toEqual([
|
|
98
|
+
"text-base",
|
|
99
|
+
"rounded-token",
|
|
100
|
+
"my-custom-active-class",
|
|
101
|
+
"w-full",
|
|
102
|
+
"px-4",
|
|
103
|
+
"py-2",
|
|
104
|
+
]);
|
|
105
|
+
expect(isSlotContainer.classes()).toEqual([
|
|
106
|
+
"text-base",
|
|
107
|
+
"rounded-token",
|
|
108
|
+
"hover:variant-ghost",
|
|
109
|
+
"w-full",
|
|
110
|
+
"px-4",
|
|
111
|
+
"py-2",
|
|
112
|
+
]);
|
|
113
|
+
expect(beautifulSlotContainer.classes()).toEqual([
|
|
114
|
+
"text-base",
|
|
115
|
+
"rounded-token",
|
|
116
|
+
"hover:variant-ghost",
|
|
117
|
+
"w-full",
|
|
118
|
+
"px-4",
|
|
119
|
+
"py-2",
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Tab } from "@headlessui/vue";
|
|
3
|
+
import { computed, inject } from "vue";
|
|
4
|
+
|
|
5
|
+
defineProps({
|
|
6
|
+
disabled: {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
default: false,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const activeClass = inject("active") as string;
|
|
13
|
+
const hoverClass = inject("hover") as string;
|
|
14
|
+
const vertical = inject("vertical") as boolean;
|
|
15
|
+
const classTab = inject("classTab") as string;
|
|
16
|
+
const hideSeparator = inject("hideSeparator") as boolean;
|
|
17
|
+
|
|
18
|
+
const tabClass = computed(() => {
|
|
19
|
+
return classTab ? classTab : "w-full px-4 py-2";
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<Tab
|
|
25
|
+
:disabled="disabled"
|
|
26
|
+
v-slot="{ selected }"
|
|
27
|
+
:class="`flex ${vertical ? 'flex-row justify-between' : 'flex-col'}`"
|
|
28
|
+
>
|
|
29
|
+
<div data-test="slot-container" :class="`text-base rounded-token ${selected ? activeClass : hoverClass} ${tabClass}`">
|
|
30
|
+
<slot />
|
|
31
|
+
</div>
|
|
32
|
+
<div
|
|
33
|
+
v-show="selected && !hideSeparator"
|
|
34
|
+
:class="`z-10 border-surface-900-50-token ${
|
|
35
|
+
vertical ? 'mr-[-2px] h-full border-r-2' : 'mb-[-2px] border-b-2'
|
|
36
|
+
}`"
|
|
37
|
+
></div>
|
|
38
|
+
</Tab>
|
|
39
|
+
</template>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { TabGroup } from "@headlessui/vue";
|
|
2
|
+
import { mount } from "@vue/test-utils";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { VTabPanel } from "..";
|
|
5
|
+
|
|
6
|
+
describe("VTabs", () => {
|
|
7
|
+
test("default props", () => {
|
|
8
|
+
const wrapper = mount({
|
|
9
|
+
template: /*html*/ `
|
|
10
|
+
<TabGroup>
|
|
11
|
+
<v-tab-panel data-test='tab-panel'>John Duck</v-tab-panel>
|
|
12
|
+
</TabGroup>
|
|
13
|
+
`,
|
|
14
|
+
components: {
|
|
15
|
+
TabGroup: TabGroup,
|
|
16
|
+
"v-tab-panel": VTabPanel,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const tabPanel = wrapper.find("[data-test='tab-panel']");
|
|
21
|
+
expect(tabPanel.text()).toEqual("John Duck");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { VTab, VTabPanel, VTabs } from "..";
|
|
4
|
+
|
|
5
|
+
describe("VTabs", () => {
|
|
6
|
+
test("hide separator", () => {
|
|
7
|
+
const wrapper = mount({
|
|
8
|
+
template: /*html*/ `
|
|
9
|
+
<v-tabs hide-separator></v-tabs>
|
|
10
|
+
`,
|
|
11
|
+
components: {
|
|
12
|
+
"v-tabs": VTabs,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const separator = wrapper.find("[data-test='vuetiful-separator']");
|
|
17
|
+
expect(separator.exists()).toBeFalsy();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("default props", () => {
|
|
21
|
+
const wrapper = mount({
|
|
22
|
+
template: /*html*/ `
|
|
23
|
+
<v-tabs>
|
|
24
|
+
<template v-slot="tabs">
|
|
25
|
+
<v-tab>John Duck</v-tab>
|
|
26
|
+
</template>
|
|
27
|
+
<v-tab-panel>John Duck Panel</v-tab-panel>
|
|
28
|
+
</v-tabs>
|
|
29
|
+
`,
|
|
30
|
+
components: {
|
|
31
|
+
"v-tabs": VTabs,
|
|
32
|
+
"v-tab": VTab,
|
|
33
|
+
"v-tab-panel": VTabPanel,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const tabList = wrapper.find("[data-test='vuetiful-tab-list']");
|
|
38
|
+
expect(tabList.classes()).toEqual([
|
|
39
|
+
"vuetiful-tab-list",
|
|
40
|
+
"flex",
|
|
41
|
+
"!rounded-bl-none",
|
|
42
|
+
"!rounded-br-none",
|
|
43
|
+
"rounded-container-token",
|
|
44
|
+
]);
|
|
45
|
+
expect(tabList.classes()).not.toContain("flex-col");
|
|
46
|
+
|
|
47
|
+
const tabPanels = wrapper.find("[data-test='vuetiful-tab-panels']");
|
|
48
|
+
expect(tabPanels.classes()).toEqual([
|
|
49
|
+
"vuetiful-tab-panels",
|
|
50
|
+
"!rounded-tl-none",
|
|
51
|
+
"!rounded-tr-none",
|
|
52
|
+
"rounded-container-token",
|
|
53
|
+
]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("vertical", () => {
|
|
57
|
+
const wrapper = mount({
|
|
58
|
+
template: /*html*/ `
|
|
59
|
+
<v-tabs vertical>
|
|
60
|
+
<template v-slot="tabs">
|
|
61
|
+
<v-tab>Tab 1</v-tab>
|
|
62
|
+
</template>
|
|
63
|
+
<v-tab-panel>Panel 1</v-tab-panel>
|
|
64
|
+
</v-tabs>
|
|
65
|
+
`,
|
|
66
|
+
components: {
|
|
67
|
+
"v-tabs": VTabs,
|
|
68
|
+
"v-tab": VTab,
|
|
69
|
+
"v-tab-panel": VTabPanel,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
const tabList = wrapper.find("[data-test='vuetiful-tab-list']");
|
|
73
|
+
expect(tabList.classes()).toEqual([
|
|
74
|
+
"vuetiful-tab-list",
|
|
75
|
+
"flex",
|
|
76
|
+
"flex-col",
|
|
77
|
+
"!rounded-br-none",
|
|
78
|
+
"!rounded-tr-none",
|
|
79
|
+
"rounded-container-token",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const tabPanels = wrapper.find("[data-test='vuetiful-tab-panels']");
|
|
83
|
+
expect(tabPanels.classes()).toEqual([
|
|
84
|
+
"vuetiful-tab-panels",
|
|
85
|
+
"!rounded-bl-none",
|
|
86
|
+
"!rounded-tl-none",
|
|
87
|
+
"rounded-container-token",
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { TabGroup, TabList, TabPanels } from "@headlessui/vue";
|
|
3
|
+
import { provide } from "vue";
|
|
4
|
+
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
hideSeparator: {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
default: false,
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
vertical: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
active: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: "",
|
|
19
|
+
},
|
|
20
|
+
hover: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: "hover:variant-ghost",
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
classPanels: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: "",
|
|
28
|
+
},
|
|
29
|
+
classTabs: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: "",
|
|
32
|
+
},
|
|
33
|
+
classTab: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "",
|
|
36
|
+
},
|
|
37
|
+
classSeparator: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: "border border-surface-400-500-token",
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
defaultIndex: {
|
|
43
|
+
type: Number,
|
|
44
|
+
default: 0,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
provide("active", props.active);
|
|
49
|
+
provide("hover", props.hover);
|
|
50
|
+
provide("vertical", props.vertical);
|
|
51
|
+
provide("classTab", props.classTab);
|
|
52
|
+
provide("hideSeparator", props.hideSeparator);
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<template>
|
|
56
|
+
<TabGroup
|
|
57
|
+
as="div"
|
|
58
|
+
:vertical="vertical"
|
|
59
|
+
:defaultIndex="defaultIndex"
|
|
60
|
+
:class="`${vertical ? 'flex' : ''}`"
|
|
61
|
+
>
|
|
62
|
+
<TabList
|
|
63
|
+
data-test="vuetiful-tab-list"
|
|
64
|
+
:class="`vuetiful-tab-list flex ${vertical ? 'flex-col' : ''} ${
|
|
65
|
+
vertical ? '!rounded-br-none !rounded-tr-none' : '!rounded-bl-none !rounded-br-none'
|
|
66
|
+
} rounded-container-token ${classTabs}`"
|
|
67
|
+
>
|
|
68
|
+
<slot name="tabs" />
|
|
69
|
+
</TabList>
|
|
70
|
+
<div
|
|
71
|
+
data-test="vuetiful-tab-separator"
|
|
72
|
+
v-if="!hideSeparator"
|
|
73
|
+
:class="`${classSeparator}`"
|
|
74
|
+
></div>
|
|
75
|
+
<TabPanels
|
|
76
|
+
data-test="vuetiful-tab-panels"
|
|
77
|
+
:class="`vuetiful-tab-panels ${
|
|
78
|
+
vertical ? '!rounded-bl-none !rounded-tl-none' : '!rounded-tl-none !rounded-tr-none'
|
|
79
|
+
} rounded-container-token ${classPanels}`"
|
|
80
|
+
>
|
|
81
|
+
<slot />
|
|
82
|
+
</TabPanels>
|
|
83
|
+
</TabGroup>
|
|
84
|
+
</template>
|