@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.
Files changed (64) hide show
  1. package/README.md +3 -4
  2. package/dist/style.css +1 -1
  3. package/dist/styles/all.css +173 -7
  4. package/dist/types/components/index.d.ts +2 -2
  5. package/dist/types/components/molecules/VListbox/VListbox.test.d.ts +1 -0
  6. package/dist/types/components/molecules/VListbox/VListbox.vue.d.ts +100 -0
  7. package/dist/types/components/molecules/VListbox/VListboxButton.test.d.ts +1 -0
  8. package/dist/types/components/molecules/VListbox/VListboxButton.vue.d.ts +23 -0
  9. package/dist/types/components/molecules/VListbox/VListboxItem.test.d.ts +1 -0
  10. package/dist/types/components/molecules/VListbox/VListboxItem.vue.d.ts +12 -0
  11. package/dist/types/components/molecules/VListbox/VListboxItems.test.d.ts +1 -0
  12. package/dist/types/components/molecules/VListbox/VListboxItems.vue.d.ts +23 -0
  13. package/dist/types/components/molecules/VListbox/VListboxLabel.test.d.ts +1 -0
  14. package/dist/types/components/molecules/VListbox/VListboxLabel.vue.d.ts +14 -0
  15. package/dist/types/components/molecules/VTabs/VTab.test.d.ts +1 -0
  16. package/dist/types/components/molecules/VTabs/VTab.vue.d.ts +14 -0
  17. package/dist/types/components/molecules/VTabs/VTabPanel.test.d.ts +1 -0
  18. package/dist/types/components/molecules/VTabs/VTabPanel.vue.d.ts +2 -0
  19. package/dist/types/components/molecules/VTabs/VTabs.test.d.ts +1 -0
  20. package/dist/types/components/molecules/VTabs/VTabs.vue.d.ts +86 -0
  21. package/dist/types/components/molecules/index.d.ts +10 -2
  22. package/dist/vuetiful.es.mjs +1333 -392
  23. package/dist/vuetiful.umd.js +12 -12
  24. package/package.json +1 -1
  25. package/src/components/atoms/VAvatar.vue +7 -1
  26. package/src/components/atoms/VBadge.test.ts +1 -1
  27. package/src/components/atoms/VChip.test.ts +1 -1
  28. package/src/components/atoms/VCodeBlock.test.ts +2 -2
  29. package/src/components/atoms/VLightSwitch.test.ts +2 -2
  30. package/src/components/atoms/VRadio/VRadioDescription.vue +3 -3
  31. package/src/components/atoms/VRadio/VRadioGroup.test.ts +4 -4
  32. package/src/components/atoms/VRadio/VRadioItem.test.ts +14 -14
  33. package/src/components/atoms/VSwitch/VSwitch.test.ts +4 -27
  34. package/src/components/atoms/VSwitch/VSwitchDescription.test.ts +1 -2
  35. package/src/components/atoms/VSwitch/VSwitchDescription.vue +2 -2
  36. package/src/components/atoms/VSwitch/VSwitchGroup.test.ts +2 -2
  37. package/src/components/atoms/VSwitch/VSwitchGroup.vue +2 -2
  38. package/src/components/atoms/VSwitch/VSwitchLabel.test.ts +3 -3
  39. package/src/components/atoms/VSwitch/VSwitchLabel.vue +3 -3
  40. package/src/components/index.ts +2 -2
  41. package/src/components/molecules/VDrawer.test.ts +2 -2
  42. package/src/components/molecules/VDrawer.vue +1 -1
  43. package/src/components/molecules/VListbox/VListbox.test.ts +119 -0
  44. package/src/components/molecules/VListbox/VListbox.vue +119 -0
  45. package/src/components/molecules/VListbox/VListboxButton.test.ts +57 -0
  46. package/src/components/molecules/VListbox/VListboxButton.vue +48 -0
  47. package/src/components/molecules/VListbox/VListboxItem.test.ts +51 -0
  48. package/src/components/molecules/VListbox/VListboxItem.vue +29 -0
  49. package/src/components/molecules/VListbox/VListboxItems.test.ts +44 -0
  50. package/src/components/molecules/VListbox/VListboxItems.vue +31 -0
  51. package/src/components/molecules/VListbox/VListboxLabel.test.ts +31 -0
  52. package/src/components/molecules/VListbox/VListboxLabel.vue +14 -0
  53. package/src/components/molecules/VPreview.test.ts +2 -2
  54. package/src/components/molecules/VRail.test.ts +2 -2
  55. package/src/components/molecules/VRailTile.test.ts +2 -2
  56. package/src/components/molecules/VShell.test.ts +2 -2
  57. package/src/components/molecules/VTabs/VTab.test.ts +122 -0
  58. package/src/components/molecules/VTabs/VTab.vue +39 -0
  59. package/src/components/molecules/VTabs/VTabPanel.test.ts +23 -0
  60. package/src/components/molecules/VTabs/VTabPanel.vue +9 -0
  61. package/src/components/molecules/VTabs/VTabs.test.ts +90 -0
  62. package/src/components/molecules/VTabs/VTabs.vue +84 -0
  63. package/src/components/molecules/index.ts +26 -2
  64. package/src/styles/elements/buttons.css +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-coaching/vuetiful",
3
- "version": "0.16.6",
3
+ "version": "0.18.0",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "onchange 'src/**/*.vue' 'src/**/*.ts' 'src/**/*.css' -- npm run build",
@@ -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 class="avatar-image" v-if="src" :src="imgSrc" :alt="alt" @error="() => (imgSrc = fallback)" />
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%"
@@ -15,4 +15,4 @@ test("VBadge using slot", () => {
15
15
 
16
16
  expect(wrapper.text()).toContain("John Duck");
17
17
  expect(wrapper.classes()).toContain("badge");
18
- });
18
+ });
@@ -15,4 +15,4 @@ test("VChip using slot", () => {
15
15
 
16
16
  expect(wrapper.text()).toContain("John Duck");
17
17
  expect(wrapper.classes()).toContain("chip");
18
- });
18
+ });
@@ -6,9 +6,9 @@ test("VCodeBlock", () => {
6
6
  expect(VCodeBlock).toBeTruthy();
7
7
  });
8
8
 
9
- // TODO: add tests
9
+ // TODO: add tests
10
10
  test("VCodeBlock using slot", () => {
11
11
  const wrapper = mount(VCodeBlock);
12
12
 
13
13
  expect(wrapper).toBeTruthy();
14
- });
14
+ });
@@ -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
+ });
@@ -1,11 +1,11 @@
1
1
  <script setup lang="ts">
2
- import { RadioGroupDescription } from '@headlessui/vue';
2
+ import { RadioGroupDescription } from "@headlessui/vue";
3
3
 
4
4
  defineProps({
5
5
  as: {
6
6
  type: String,
7
- default: 'p',
8
- }
7
+ default: "p",
8
+ },
9
9
  });
10
10
  </script>
11
11
 
@@ -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()['update:modelValue'][0]).toEqual(["Jane Duck"]);
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
  });
@@ -1,10 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { SwitchDescription } from '@headlessui/vue';
2
+ import { SwitchDescription } from "@headlessui/vue";
3
3
 
4
4
  defineProps({
5
5
  as: {
6
6
  type: String,
7
- default: 'p',
7
+ default: "p",
8
8
  },
9
9
  });
10
10
  </script>
@@ -22,5 +22,5 @@ describe("VSwitchGroup props", () => {
22
22
 
23
23
  const divEl = wrapper.find("div");
24
24
  expect(divEl).not.toBeUndefined();
25
- })
26
- })
25
+ });
26
+ });
@@ -1,10 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { SwitchGroup } from '@headlessui/vue';
2
+ import { SwitchGroup } from "@headlessui/vue";
3
3
 
4
4
  defineProps({
5
5
  as: {
6
6
  type: String,
7
- default: 'template',
7
+ default: "template",
8
8
  },
9
9
  });
10
10
  </script>
@@ -67,7 +67,7 @@ describe("VSwitchLabel props", () => {
67
67
  });
68
68
 
69
69
  const label = wrapper.findComponent(VSwitchLabel);
70
- expect(label.props('passive')).toBe(false);
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('passive')).toBe(true);
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 '@headlessui/vue';
2
+ import { SwitchLabel } from "@headlessui/vue";
3
3
 
4
4
  defineProps({
5
5
  as: {
6
6
  type: String,
7
- default: 'p',
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
 
@@ -1,2 +1,2 @@
1
- export * from './atoms';
2
- export * from './molecules';
1
+ export * from "./atoms";
2
+ export * from "./molecules";
@@ -6,9 +6,9 @@ test("VDrawer", () => {
6
6
  expect(VDrawer).toBeTruthy();
7
7
  });
8
8
 
9
- // TODO: add tests
9
+ // TODO: add tests
10
10
  test("VDrawer using slot", () => {
11
11
  const wrapper = mount(VDrawer);
12
12
 
13
13
  expect(wrapper).toBeTruthy();
14
- });
14
+ });
@@ -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 z-40 ${regionBackdrop}`"
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
+ });