@code-coaching/vuetiful 0.16.5 → 0.17.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/README.md +4 -5
- package/dist/style.css +1 -1
- package/dist/styles/all.css +70 -2
- 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/index.d.ts +6 -1
- package/dist/vuetiful.es.mjs +792 -185
- package/dist/vuetiful.umd.js +13 -13
- 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/index.ts +17 -1
- package/src/styles/elements/buttons.css +1 -1
package/package.json
CHANGED
|
@@ -33,7 +33,13 @@ const fillInitials = computed(() => {
|
|
|
33
33
|
<figure
|
|
34
34
|
:class="`avatar isolate flex aspect-square items-center justify-center overflow-hidden font-semibold ${rounded} ${width}`"
|
|
35
35
|
>
|
|
36
|
-
<img
|
|
36
|
+
<img
|
|
37
|
+
class="avatar-image"
|
|
38
|
+
v-if="src"
|
|
39
|
+
:src="imgSrc"
|
|
40
|
+
:alt="alt"
|
|
41
|
+
@error="() => (imgSrc = fallback)"
|
|
42
|
+
/>
|
|
37
43
|
<svg v-else class="avatar-initials h-full w-full" viewBox="0 0 512 512">
|
|
38
44
|
<text
|
|
39
45
|
x="50%"
|
|
@@ -11,10 +11,10 @@ test("VLightSwitch", () => {
|
|
|
11
11
|
expect(VLightSwitch).toBeTruthy();
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
// TODO: add tests
|
|
14
|
+
// TODO: add tests
|
|
15
15
|
test("VLightSwitch using slot", () => {
|
|
16
16
|
window.matchMedia = matchMediaMock(MODE.LIGHT) as any;
|
|
17
17
|
const wrapper = mount(VLightSwitch);
|
|
18
18
|
|
|
19
19
|
expect(wrapper).toBeTruthy();
|
|
20
|
-
});
|
|
20
|
+
});
|
|
@@ -75,7 +75,7 @@ describe("VRadioGroup v-model", () => {
|
|
|
75
75
|
|
|
76
76
|
wrapper.setProps({ modelValue: "Jane Duck" });
|
|
77
77
|
await wrapper.vm.$nextTick();
|
|
78
|
-
expect(wrapper.emitted()[
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
})
|
|
78
|
+
expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["Jane Duck"]);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -42,7 +42,7 @@ describe("VRadioItem slot states", () => {
|
|
|
42
42
|
|
|
43
43
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
44
44
|
expect(itemEl.element.classList.contains("variant-filled")).toBe(true);
|
|
45
|
-
expect(itemEl.element.classList.contains("hover:variant-ghost")).toBe(false);
|
|
45
|
+
expect(itemEl.element.classList.contains("hover:variant-ghost")).toBe(false);
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
test("should have the default hover class", () => {
|
|
@@ -64,9 +64,9 @@ describe("VRadioItem slot states", () => {
|
|
|
64
64
|
|
|
65
65
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
66
66
|
expect(itemEl.element.classList.contains("variant-filled")).toBe(false);
|
|
67
|
-
expect(itemEl.element.classList.contains("hover:variant-ghost")).toBe(true);
|
|
67
|
+
expect(itemEl.element.classList.contains("hover:variant-ghost")).toBe(true);
|
|
68
68
|
});
|
|
69
|
-
})
|
|
69
|
+
});
|
|
70
70
|
describe("given v-slot checked is true", () => {
|
|
71
71
|
test("should have the active class", () => {
|
|
72
72
|
const wrapper = mount({
|
|
@@ -91,7 +91,7 @@ describe("VRadioItem slot states", () => {
|
|
|
91
91
|
|
|
92
92
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
93
93
|
expect(itemEl.element.classList.contains("custom-active-class")).toBe(true);
|
|
94
|
-
expect(itemEl.element.classList.contains("custom-hover-class")).toBe(false);
|
|
94
|
+
expect(itemEl.element.classList.contains("custom-hover-class")).toBe(false);
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
@@ -118,10 +118,10 @@ describe("VRadioItem slot states", () => {
|
|
|
118
118
|
});
|
|
119
119
|
|
|
120
120
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
121
|
-
expect(itemEl.element.classList.contains("custom-active-class")).toBe(false);
|
|
122
|
-
expect(itemEl.element.classList.contains("custom-hover-class")).toBe(true);
|
|
121
|
+
expect(itemEl.element.classList.contains("custom-active-class")).toBe(false);
|
|
122
|
+
expect(itemEl.element.classList.contains("custom-hover-class")).toBe(true);
|
|
123
123
|
});
|
|
124
|
-
})
|
|
124
|
+
});
|
|
125
125
|
|
|
126
126
|
describe("given v-slot disabled is false", () => {
|
|
127
127
|
test("should not have the disabled classes", () => {
|
|
@@ -146,9 +146,9 @@ describe("VRadioItem slot states", () => {
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
149
|
-
expect(itemEl.element.classList.contains("cursor-pointer")).toBe(true);
|
|
150
|
-
expect(itemEl.element.classList.contains("cursor-not-allowed")).toBe(false);
|
|
151
|
-
expect(itemEl.element.classList.contains("opacity-50")).toBe(false);
|
|
149
|
+
expect(itemEl.element.classList.contains("cursor-pointer")).toBe(true);
|
|
150
|
+
expect(itemEl.element.classList.contains("cursor-not-allowed")).toBe(false);
|
|
151
|
+
expect(itemEl.element.classList.contains("opacity-50")).toBe(false);
|
|
152
152
|
});
|
|
153
153
|
});
|
|
154
154
|
|
|
@@ -175,9 +175,9 @@ describe("VRadioItem slot states", () => {
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
const itemEl = wrapper.find("[data-test='item']").find("div");
|
|
178
|
-
expect(itemEl.element.classList.contains("cursor-pointer")).toBe(false);
|
|
179
|
-
expect(itemEl.element.classList.contains("cursor-not-allowed")).toBe(true);
|
|
180
|
-
expect(itemEl.element.classList.contains("opacity-50")).toBe(true);
|
|
178
|
+
expect(itemEl.element.classList.contains("cursor-pointer")).toBe(false);
|
|
179
|
+
expect(itemEl.element.classList.contains("cursor-not-allowed")).toBe(true);
|
|
180
|
+
expect(itemEl.element.classList.contains("opacity-50")).toBe(true);
|
|
181
181
|
});
|
|
182
182
|
});
|
|
183
|
-
});
|
|
183
|
+
});
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
import { mount } from "@vue/test-utils";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
3
|
import VSwitch from "./VSwitch.vue";
|
|
4
|
-
import { ref } from "vue";
|
|
5
|
-
|
|
6
|
-
describe("VSwitch slots", () => {
|
|
7
|
-
test("should not add screen reader text by default", () => {
|
|
8
|
-
const wrapper = mount(VSwitch);
|
|
9
|
-
|
|
10
|
-
const srOnly = wrapper.find(".sr-only");
|
|
11
|
-
expect(srOnly.exists()).toBe(false);
|
|
12
|
-
});
|
|
13
|
-
test("should add screen reader text if default slot is present", () => {
|
|
14
|
-
const wrapper = mount(VSwitch, {
|
|
15
|
-
slots: {
|
|
16
|
-
default: "John Duck",
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const srOnly = wrapper.find(".sr-only");
|
|
21
|
-
expect(srOnly.text()).toContain("John Duck");
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
4
|
|
|
25
5
|
describe("VSwitch props", () => {
|
|
26
6
|
test("defaults", () => {
|
|
@@ -102,9 +82,6 @@ describe("VSwitch props", () => {
|
|
|
102
82
|
const track = wrapper.find(".slide-toggle-track");
|
|
103
83
|
expect(track.attributes("class")).toContain("custom");
|
|
104
84
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
85
|
});
|
|
109
86
|
|
|
110
87
|
describe("VSwitch events", () => {
|
|
@@ -112,10 +89,10 @@ describe("VSwitch events", () => {
|
|
|
112
89
|
const wrapper = mount(VSwitch, {
|
|
113
90
|
props: {
|
|
114
91
|
modelValue: false,
|
|
115
|
-
}
|
|
92
|
+
},
|
|
116
93
|
});
|
|
117
94
|
|
|
118
|
-
await wrapper.setProps({ modelValue: true});
|
|
95
|
+
await wrapper.setProps({ modelValue: true });
|
|
119
96
|
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
|
|
120
|
-
})
|
|
121
|
-
})
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mount } from "@vue/test-utils";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
|
-
import VSwitchGroup from "./VSwitchGroup.vue";
|
|
4
3
|
import VSwitchDescription from "./VSwitchDescription.vue";
|
|
4
|
+
import VSwitchGroup from "./VSwitchGroup.vue";
|
|
5
5
|
|
|
6
6
|
test("VSwitchDescription using slot", () => {
|
|
7
7
|
const wrapper = mount({
|
|
@@ -52,5 +52,4 @@ describe("VSwitchDescription props", () => {
|
|
|
52
52
|
const label = wrapper.find("[data-test='label']");
|
|
53
53
|
expect(label.element).toBeInstanceOf(HTMLDivElement);
|
|
54
54
|
});
|
|
55
|
-
|
|
56
55
|
});
|
|
@@ -67,7 +67,7 @@ describe("VSwitchLabel props", () => {
|
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
const label = wrapper.findComponent(VSwitchLabel);
|
|
70
|
-
expect(label.props(
|
|
70
|
+
expect(label.props("passive")).toBe(false);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
test("custom 'passive' prop'", () => {
|
|
@@ -84,6 +84,6 @@ describe("VSwitchLabel props", () => {
|
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
const label = wrapper.findComponent(VSwitchLabel);
|
|
87
|
-
expect(label.props(
|
|
88
|
-
})
|
|
87
|
+
expect(label.props("passive")).toBe(true);
|
|
88
|
+
});
|
|
89
89
|
});
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { SwitchLabel } from
|
|
2
|
+
import { SwitchLabel } from "@headlessui/vue";
|
|
3
3
|
|
|
4
4
|
defineProps({
|
|
5
5
|
as: {
|
|
6
6
|
type: String,
|
|
7
|
-
default:
|
|
7
|
+
default: "p",
|
|
8
8
|
},
|
|
9
9
|
passive: {
|
|
10
10
|
type: Boolean,
|
|
11
11
|
default: false,
|
|
12
|
-
}
|
|
12
|
+
},
|
|
13
13
|
});
|
|
14
14
|
</script>
|
|
15
15
|
|
package/src/components/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./atoms";
|
|
2
|
+
export * from "./molecules";
|
|
@@ -79,7 +79,7 @@ onMounted(() => {
|
|
|
79
79
|
<div
|
|
80
80
|
v-if="drawer.open"
|
|
81
81
|
ref="elemBackdrop"
|
|
82
|
-
:class="`drawer-backdrop backdrop-blur-xs fixed bottom-0 left-0 right-0 top-0 flex bg-surface-backdrop-token
|
|
82
|
+
:class="`drawer-backdrop backdrop-blur-xs fixed bottom-0 left-0 right-0 top-0 z-40 flex bg-surface-backdrop-token ${regionBackdrop}`"
|
|
83
83
|
@mousedown="onBackdropInteraction"
|
|
84
84
|
@touchstart="onBackdropInteraction"
|
|
85
85
|
></div>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { mount } from "@vue/test-utils";
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
import { VListboxItem } from "..";
|
|
5
|
+
import VListbox from "./VListbox.vue";
|
|
6
|
+
|
|
7
|
+
describe("VListbox", () => {
|
|
8
|
+
test("default props", () => {
|
|
9
|
+
const wrapper = mount(VListbox);
|
|
10
|
+
|
|
11
|
+
expect(wrapper.props()).toEqual({
|
|
12
|
+
active: "variant-filled",
|
|
13
|
+
background: "bg-surface-200-700-token",
|
|
14
|
+
buttonText: "Select an option",
|
|
15
|
+
by: undefined,
|
|
16
|
+
display: undefined,
|
|
17
|
+
horizontal: false,
|
|
18
|
+
hover: "variant-ghost",
|
|
19
|
+
labelClasses: false,
|
|
20
|
+
labelText: undefined,
|
|
21
|
+
modelValue: undefined,
|
|
22
|
+
multiple: false,
|
|
23
|
+
text: "text-surface-900 dark:text-surface-50",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("modelValue", () => {
|
|
28
|
+
test("should update modelValue", async () => {
|
|
29
|
+
const choice = ref();
|
|
30
|
+
const wrapper = mount({
|
|
31
|
+
setup() {
|
|
32
|
+
return { choice };
|
|
33
|
+
},
|
|
34
|
+
template: /*html*/ `
|
|
35
|
+
<v-listbox class="w-60" v-model="choice">
|
|
36
|
+
<v-listbox-item data-test="vuetiful" value="vuetiful">Vuetiful</v-listbox-item>
|
|
37
|
+
<v-listbox-item data-test="is" value="is">Is</v-listbox-item>
|
|
38
|
+
<v-listbox-item data-test="beautiful" value="beautiful">Beautiful</v-listbox-item>
|
|
39
|
+
</v-listbox>
|
|
40
|
+
`,
|
|
41
|
+
components: {
|
|
42
|
+
"v-listbox": VListbox,
|
|
43
|
+
"v-listbox-item": VListboxItem,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
48
|
+
await listbox.find("[data-test='v-listbox-button']").trigger("click");
|
|
49
|
+
await listbox.find("[data-test='vuetiful']").trigger("click");
|
|
50
|
+
expect(choice.value).toBe("vuetiful");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("object modelValue", () => {
|
|
55
|
+
test("should use display prop", async () => {
|
|
56
|
+
const choice = ref();
|
|
57
|
+
const wrapper = mount({
|
|
58
|
+
setup() {
|
|
59
|
+
return { choice };
|
|
60
|
+
},
|
|
61
|
+
template: /*html*/ `
|
|
62
|
+
<v-listbox class="w-60" v-model="choice" by="id" display="name">
|
|
63
|
+
<v-listbox-item data-test="vuetiful" :value="{ name: 'vuetiful', id: 0 }">Vuetiful</v-listbox-item>
|
|
64
|
+
<v-listbox-item data-test="is" :value="{ name: 'is', id: 1 }">Is</v-listbox-item>
|
|
65
|
+
<v-listbox-item data-test="beautiful" :value="{ name: 'Beautiful', id: 2 }">Beautiful</v-listbox-item>
|
|
66
|
+
</v-listbox>
|
|
67
|
+
`,
|
|
68
|
+
components: {
|
|
69
|
+
"v-listbox": VListbox,
|
|
70
|
+
"v-listbox-item": VListboxItem,
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
75
|
+
const button = listbox.find("[data-test='v-listbox-button']");
|
|
76
|
+
expect(button.text()).toBe("Select an option");
|
|
77
|
+
await button.trigger("click");
|
|
78
|
+
await listbox.find("[data-test='vuetiful']").trigger("click");
|
|
79
|
+
|
|
80
|
+
expect(choice.value).toEqual({ name: "vuetiful", id: 0 });
|
|
81
|
+
expect(button.text()).toBe("vuetiful");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("multiple", () => {
|
|
86
|
+
test("should update modelValue and button text", async () => {
|
|
87
|
+
const choice = ref([]);
|
|
88
|
+
const wrapper = mount({
|
|
89
|
+
setup() {
|
|
90
|
+
return { choice };
|
|
91
|
+
},
|
|
92
|
+
template: /*html*/ `
|
|
93
|
+
<v-listbox class="w-60" v-model="choice" multiple>
|
|
94
|
+
<v-listbox-item data-test="vuetiful" value="vuetiful">Vuetiful</v-listbox-item>
|
|
95
|
+
<v-listbox-item data-test="is" value="is">Is</v-listbox-item>
|
|
96
|
+
<v-listbox-item data-test="beautiful" value="beautiful">Beautiful</v-listbox-item>
|
|
97
|
+
</v-listbox>
|
|
98
|
+
`,
|
|
99
|
+
components: {
|
|
100
|
+
"v-listbox": VListbox,
|
|
101
|
+
"v-listbox-item": VListboxItem,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const listbox = wrapper.find("[data-test='v-listbox']");
|
|
106
|
+
const button = listbox.find("[data-test='v-listbox-button']");
|
|
107
|
+
expect(button.text()).toBe("Select an option");
|
|
108
|
+
|
|
109
|
+
await button.trigger("click");
|
|
110
|
+
await listbox.find("[data-test='vuetiful']").trigger("click");
|
|
111
|
+
expect(choice.value).toEqual(["vuetiful"]);
|
|
112
|
+
expect(button.text()).toBe("vuetiful");
|
|
113
|
+
|
|
114
|
+
await listbox.find("[data-test='is']").trigger("click");
|
|
115
|
+
expect(choice.value).toEqual(["vuetiful", "is"]);
|
|
116
|
+
expect(button.text()).toBe("2 options selected");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { Listbox } from "@headlessui/vue";
|
|
3
|
+
import { computed, provide, ref, watch } from "vue";
|
|
4
|
+
import VListboxButton from "./VListboxButton.vue";
|
|
5
|
+
import VListboxItems from "./VListboxItems.vue";
|
|
6
|
+
import VListboxLabel from "./VListboxLabel.vue";
|
|
7
|
+
|
|
8
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
by: {
|
|
11
|
+
type: String,
|
|
12
|
+
},
|
|
13
|
+
display: {
|
|
14
|
+
type: String,
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
labelText: {
|
|
18
|
+
type: String,
|
|
19
|
+
},
|
|
20
|
+
labelClasses: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
default: false,
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
buttonText: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: "Select an option",
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
modelValue: Listbox.props["modelValue"],
|
|
31
|
+
horizontal: {
|
|
32
|
+
type: Boolean,
|
|
33
|
+
default: false,
|
|
34
|
+
},
|
|
35
|
+
multiple: {
|
|
36
|
+
type: Boolean,
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
active: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: "variant-filled",
|
|
43
|
+
},
|
|
44
|
+
hover: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: "variant-ghost",
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
background: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: "bg-surface-200-700-token",
|
|
52
|
+
},
|
|
53
|
+
text: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: "text-surface-900 dark:text-surface-50",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const parentModelValue = ref(props.modelValue);
|
|
60
|
+
watch(
|
|
61
|
+
() => props.modelValue,
|
|
62
|
+
(newValue) => {
|
|
63
|
+
parentModelValue.value = newValue;
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
watch(
|
|
67
|
+
() => parentModelValue.value,
|
|
68
|
+
(newValue) => {
|
|
69
|
+
emit("update:modelValue", newValue);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
provide("active", props.active);
|
|
74
|
+
provide("hover", props.hover);
|
|
75
|
+
provide("background", props.background);
|
|
76
|
+
provide("text", props.text);
|
|
77
|
+
provide("horizontal", props.horizontal);
|
|
78
|
+
|
|
79
|
+
const showText = computed(() => {
|
|
80
|
+
if (props.display && parentModelValue.value) return parentModelValue.value[props.display];
|
|
81
|
+
|
|
82
|
+
const length = parentModelValue.value?.length;
|
|
83
|
+
if (props.multiple && length === 0) return props.buttonText;
|
|
84
|
+
if (props.multiple && length === 1) return parentModelValue.value[0];
|
|
85
|
+
if (props.multiple && length > 1) return `${length} options selected`; // TODO: i18n
|
|
86
|
+
|
|
87
|
+
if (parentModelValue.value) return parentModelValue.value;
|
|
88
|
+
|
|
89
|
+
return props.buttonText;
|
|
90
|
+
});
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<template>
|
|
94
|
+
<!-- There is some odd behavior with test coverge, v-model must be the last property in this component -->
|
|
95
|
+
<Listbox
|
|
96
|
+
data-test="v-listbox"
|
|
97
|
+
as="div"
|
|
98
|
+
:by="by"
|
|
99
|
+
:multiple="multiple"
|
|
100
|
+
class="vuetiful-listbox relative rounded-container-token"
|
|
101
|
+
v-model="parentModelValue"
|
|
102
|
+
>
|
|
103
|
+
<v-listbox-label v-if="labelText" :class="labelClasses">{{ labelText }}</v-listbox-label>
|
|
104
|
+
<v-listbox-button data-test="v-listbox-button">{{ showText }}</v-listbox-button>
|
|
105
|
+
<!-- TODO: Add configurable transition -->
|
|
106
|
+
<transition
|
|
107
|
+
enter-active-class="transition duration-150 ease-in-out"
|
|
108
|
+
enter-from-class="opacity-0"
|
|
109
|
+
enter-to-class="opacity-100"
|
|
110
|
+
leave-active-class="transition duration-150 ease-in-out"
|
|
111
|
+
leave-from-class="opacity-100"
|
|
112
|
+
leave-to-class="opacity-0"
|
|
113
|
+
>
|
|
114
|
+
<v-listbox-items class="absolute mt-1 min-w-full">
|
|
115
|
+
<slot />
|
|
116
|
+
</v-listbox-items>
|
|
117
|
+
</transition>
|
|
118
|
+
</Listbox>
|
|
119
|
+
</template>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Listbox } from "@headlessui/vue";
|
|
2
|
+
import { mount } from "@vue/test-utils";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import VListboxButton from "./VListboxButton.vue";
|
|
5
|
+
|
|
6
|
+
describe("VListboxButton props", () => {
|
|
7
|
+
test("defaults", () => {
|
|
8
|
+
const wrapper = mount({
|
|
9
|
+
template: /* html */ `
|
|
10
|
+
<Listbox>
|
|
11
|
+
<v-listbox-button data-test="v-listbox-button">John Duck</v-listbox-button>
|
|
12
|
+
</Listbox>
|
|
13
|
+
`,
|
|
14
|
+
components: {
|
|
15
|
+
"v-listbox-button": VListboxButton,
|
|
16
|
+
Listbox: Listbox,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const listboxButton = wrapper.find("[data-test='v-listbox-button']");
|
|
21
|
+
expect(listboxButton.element.tagName).toBe("BUTTON");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("custom 'as' prop", () => {
|
|
25
|
+
const wrapper = mount({
|
|
26
|
+
template: /* html */ `
|
|
27
|
+
<Listbox>
|
|
28
|
+
<v-listbox-button as="div" data-test="v-listbox-button">John Duck</v-listbox-button>
|
|
29
|
+
</Listbox>
|
|
30
|
+
`,
|
|
31
|
+
components: {
|
|
32
|
+
"v-listbox-button": VListboxButton,
|
|
33
|
+
Listbox: Listbox,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const listboxButton = wrapper.find("[data-test='v-listbox-button']");
|
|
38
|
+
expect(listboxButton.element.tagName).toBe("DIV");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("should hide icon", () => {
|
|
42
|
+
const wrapper = mount({
|
|
43
|
+
template: /* html */ `
|
|
44
|
+
<Listbox>
|
|
45
|
+
<v-listbox-button hide-icon data-test="v-listbox-button">John Duck</v-listbox-button>
|
|
46
|
+
</Listbox>
|
|
47
|
+
`,
|
|
48
|
+
components: {
|
|
49
|
+
"v-listbox-button": VListboxButton,
|
|
50
|
+
Listbox: Listbox,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const listboxButton = wrapper.find("[data-test='v-listbox-button']");
|
|
55
|
+
expect(listboxButton.find("svg").exists()).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|