@code-coaching/vuetiful 0.22.0 → 0.23.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 (105) hide show
  1. package/README.md +1 -1
  2. package/dist/style.css +2 -2
  3. package/dist/types/components/VBootstrap.vue.d.ts +15 -0
  4. package/dist/types/components/atoms/VAvatar.vue.d.ts +37 -10
  5. package/dist/types/components/atoms/VBadge.vue.d.ts +22 -1
  6. package/dist/types/components/atoms/VButton.vue.d.ts +27 -0
  7. package/dist/types/components/atoms/VChip.vue.d.ts +22 -1
  8. package/dist/types/components/atoms/VRadio/VRadioGroup.vue.d.ts +10 -1
  9. package/dist/types/components/atoms/VRadio/VRadioItem.vue.d.ts +2 -2
  10. package/dist/types/components/atoms/VSwitch/VSwitch.vue.d.ts +24 -15
  11. package/dist/types/components/atoms/index.d.ts +1 -2
  12. package/dist/types/components/molecules/VAccordion/VAccordion.vue.d.ts +15 -6
  13. package/dist/types/components/molecules/VAccordion/VAccordionItem.vue.d.ts +11 -1
  14. package/dist/types/components/molecules/VAlert.vue.d.ts +39 -3
  15. package/dist/types/components/molecules/VCard/VCard.vue.d.ts +5 -5
  16. package/dist/types/components/molecules/VCard/VCardBody.vue.d.ts +13 -1
  17. package/dist/types/components/molecules/VCard/VCardFooter.vue.d.ts +22 -1
  18. package/dist/types/components/molecules/VCard/VCardHeader.vue.d.ts +22 -1
  19. package/dist/types/components/{atoms → molecules}/VCodeBlock.vue.d.ts +36 -9
  20. package/dist/types/components/molecules/VListbox/VListbox.vue.d.ts +51 -14
  21. package/dist/types/components/molecules/VListbox/VListboxItem.vue.d.ts +13 -3
  22. package/dist/types/components/molecules/VListbox/VListboxItems.vue.d.ts +9 -0
  23. package/dist/types/components/molecules/VTabs/VTab.vue.d.ts +9 -0
  24. package/dist/types/components/molecules/VTabs/VTabs.vue.d.ts +18 -0
  25. package/dist/types/components/molecules/index.d.ts +4 -3
  26. package/dist/types/props/index.d.ts +1 -0
  27. package/dist/types/props/props.d.ts +14 -0
  28. package/dist/types/services/index.d.ts +5 -3
  29. package/dist/types/services/settings.service.d.ts +132 -0
  30. package/dist/types/services/settings.service.test.d.ts +1 -0
  31. package/dist/types/types/index.d.ts +53 -0
  32. package/dist/vuetiful.es.mjs +920 -557
  33. package/dist/vuetiful.umd.js +23 -10
  34. package/package.json +1 -1
  35. package/src/components/VBootstrap.vue +62 -0
  36. package/src/components/atoms/VAvatar.test.ts +98 -28
  37. package/src/components/atoms/VAvatar.vue +46 -13
  38. package/src/components/atoms/VBadge.test.ts +10 -0
  39. package/src/components/atoms/VBadge.vue +13 -1
  40. package/src/components/atoms/VButton.test.ts +58 -0
  41. package/src/components/atoms/VButton.vue +31 -2
  42. package/src/components/atoms/VChip.test.ts +26 -11
  43. package/src/components/atoms/VChip.vue +13 -1
  44. package/src/components/atoms/VRadio/VRadioDescription.vue +1 -1
  45. package/src/components/atoms/VRadio/VRadioGroup.test.ts +7 -7
  46. package/src/components/atoms/VRadio/VRadioGroup.vue +16 -5
  47. package/src/components/atoms/VRadio/VRadioItem.vue +12 -8
  48. package/src/components/atoms/VRadio/VRadioLabel.vue +1 -1
  49. package/src/components/atoms/VSwitch/VSwitch.test.ts +20 -18
  50. package/src/components/atoms/VSwitch/VSwitch.vue +29 -17
  51. package/src/components/atoms/VSwitch/VSwitchDescription.vue +1 -1
  52. package/src/components/atoms/VSwitch/VSwitchGroup.vue +2 -2
  53. package/src/components/atoms/VSwitch/VSwitchLabel.vue +1 -1
  54. package/src/components/atoms/index.ts +0 -2
  55. package/src/components/molecules/VAccordion/VAccordion.test.ts +11 -0
  56. package/src/components/molecules/VAccordion/VAccordion.vue +16 -7
  57. package/src/components/molecules/VAccordion/VAccordionItem.test.ts +65 -16
  58. package/src/components/molecules/VAccordion/VAccordionItem.vue +53 -32
  59. package/src/components/molecules/VAlert.test.ts +11 -1
  60. package/src/components/molecules/VAlert.vue +33 -7
  61. package/src/components/molecules/VCard/VCard.test.ts +1 -1
  62. package/src/components/molecules/VCard/VCard.vue +12 -7
  63. package/src/components/molecules/VCard/VCardBody.test.ts +18 -0
  64. package/src/components/molecules/VCard/VCardBody.vue +16 -1
  65. package/src/components/molecules/VCard/VCardFooter.test.ts +18 -0
  66. package/src/components/molecules/VCard/VCardFooter.vue +21 -3
  67. package/src/components/molecules/VCard/VCardHeader.test.ts +18 -0
  68. package/src/components/molecules/VCard/VCardHeader.vue +26 -5
  69. package/src/components/molecules/VCodeBlock.test.ts +133 -0
  70. package/src/components/molecules/VCodeBlock.vue +120 -0
  71. package/src/components/molecules/VListbox/VListbox.test.ts +42 -15
  72. package/src/components/molecules/VListbox/VListbox.vue +44 -15
  73. package/src/components/molecules/VListbox/VListboxButton.test.ts +15 -6
  74. package/src/components/molecules/VListbox/VListboxButton.vue +10 -1
  75. package/src/components/molecules/VListbox/VListboxItem.test.ts +2 -2
  76. package/src/components/molecules/VListbox/VListboxItem.vue +18 -7
  77. package/src/components/molecules/VListbox/VListboxItems.test.ts +2 -2
  78. package/src/components/molecules/VListbox/VListboxItems.vue +18 -5
  79. package/src/components/molecules/VListbox/VListboxLabel.test.ts +1 -2
  80. package/src/components/molecules/VListbox/VListboxLabel.vue +1 -1
  81. package/src/components/molecules/VPreview.vue +9 -5
  82. package/src/components/molecules/{VRail.test.ts → VRail/VRail.test.ts} +1 -1
  83. package/src/components/molecules/{VRail.vue → VRail/VRail.vue} +6 -6
  84. package/src/components/molecules/VRail/VRailTile.test.ts +99 -0
  85. package/src/components/molecules/{VRailTile.vue → VRail/VRailTile.vue} +4 -6
  86. package/src/components/molecules/VTabs/VTab.test.ts +7 -3
  87. package/src/components/molecules/VTabs/VTab.vue +20 -5
  88. package/src/components/molecules/VTabs/VTabPanel.vue +2 -2
  89. package/src/components/molecules/VTabs/VTabs.test.ts +4 -2
  90. package/src/components/molecules/VTabs/VTabs.vue +32 -8
  91. package/src/components/molecules/index.ts +5 -2
  92. package/src/props/index.ts +1 -0
  93. package/src/props/props.ts +62 -0
  94. package/src/services/index.ts +5 -3
  95. package/src/services/settings.service.test.ts +17 -0
  96. package/src/services/settings.service.ts +136 -0
  97. package/src/types/index.ts +58 -0
  98. package/src/components/atoms/VCodeBlock.test.ts +0 -14
  99. package/src/components/atoms/VCodeBlock.vue +0 -92
  100. package/src/components/molecules/VRailTile.test.ts +0 -14
  101. /package/dist/types/components/{atoms → molecules}/VCodeBlock.test.d.ts +0 -0
  102. /package/dist/types/components/molecules/{VRail.test.d.ts → VRail/VRail.test.d.ts} +0 -0
  103. /package/dist/types/components/molecules/{VRail.vue.d.ts → VRail/VRail.vue.d.ts} +0 -0
  104. /package/dist/types/components/molecules/{VRailTile.test.d.ts → VRail/VRailTile.test.d.ts} +0 -0
  105. /package/dist/types/components/molecules/{VRailTile.vue.d.ts → VRail/VRailTile.vue.d.ts} +0 -0
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import { useSettings } from "@/services";
2
3
  import { PropType, computed } from "vue";
3
4
 
4
5
  const emit = defineEmits(["close"]);
@@ -17,9 +18,27 @@ const props = defineProps({
17
18
  default: true,
18
19
  },
19
20
  type: {
20
- type: String as PropType<"info" | "success" | "warning" | "error">,
21
+ type: String as PropType<"info" | "success" | "warning" | "error" | "">,
21
22
  default: "",
22
23
  },
24
+
25
+ classPre: {
26
+ type: String,
27
+ default: "",
28
+ },
29
+ classMessage: {
30
+ type: String,
31
+ default: "",
32
+ },
33
+ classClose: {
34
+ type: String,
35
+ default: "",
36
+ },
37
+
38
+ unstyled: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
23
42
  });
24
43
 
25
44
  const typeBackground = computed(() => {
@@ -32,8 +51,8 @@ const typeBackground = computed(() => {
32
51
  return "variant-filled-warning";
33
52
  case "error":
34
53
  return "variant-filled-error";
35
- default:
36
- return "variant-filled-primary";
54
+ case "":
55
+ return "";
37
56
  }
38
57
  });
39
58
 
@@ -43,14 +62,21 @@ const handleKeydown = (event: KeyboardEvent) => {
43
62
  close();
44
63
  }
45
64
  };
65
+
66
+ const { settings } = useSettings();
67
+ const isUnstyled = settings.global.unstyled || settings.components.alert.unstyled || props.unstyled;
46
68
  </script>
47
69
 
48
70
  <template>
49
71
  <aside
50
72
  v-if="show"
51
- :class="`vuetiful-alert flex w-full flex-row items-center gap-4 p-4 border-token rounded-container-token ${typeBackground}`"
73
+ :class="`vuetiful-alert flex ${
74
+ isUnstyled
75
+ ? ''
76
+ : 'w-full items-center gap-4 p-4 border-token rounded-container-token'
77
+ } ${typeBackground}`"
52
78
  >
53
- <div v-if="!hideIcon">
79
+ <div v-if="!hideIcon" :class="`vuetiful-alert-pre ${classPre}`">
54
80
  <slot v-if="$slots.pre" name="pre" />
55
81
  <template v-if="!$slots.pre">
56
82
  <!-- https://fontawesome.com/icons/circle-info?f=classic&s=solid -->
@@ -103,7 +129,7 @@ const handleKeydown = (event: KeyboardEvent) => {
103
129
  </template>
104
130
  </div>
105
131
 
106
- <div class="vuetiful-alert-message flex-auto">
132
+ <div :class="`vuetiful-alert-message ${isUnstyled ? '' : 'flex-auto'} ${classMessage}`">
107
133
  <slot />
108
134
  </div>
109
135
 
@@ -115,7 +141,7 @@ const handleKeydown = (event: KeyboardEvent) => {
115
141
  tabindex="0"
116
142
  @keydown="handleKeydown"
117
143
  @click="close"
118
- class="icon hover:cursor-pointer"
144
+ :class="`vuetiful-alert-close-icon icon hover:cursor-pointer ${classClose}`"
119
145
  xmlns="http://www.w3.org/2000/svg"
120
146
  viewBox="0 0 384 512"
121
147
  >
@@ -9,8 +9,8 @@ describe("VCard", () => {
9
9
  background: "bg-surface-200-700-token",
10
10
  clickable: false,
11
11
  hideSeparator: false,
12
- horizontal: false,
13
12
  text: "text-surface-900-50-token",
13
+ unstyled: false,
14
14
  });
15
15
  });
16
16
 
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { useSettings } from "@/index";
3
+ import { unstyledProp } from "@/props";
2
4
  import { provide } from "vue";
3
5
 
4
6
  const emit = defineEmits(["click"]);
@@ -16,14 +18,12 @@ const props = defineProps({
16
18
  type: String,
17
19
  default: "text-surface-900-50-token",
18
20
  },
19
- horizontal: {
20
- type: Boolean,
21
- default: false,
22
- },
23
21
  clickable: {
24
22
  type: Boolean,
25
23
  default: false,
26
24
  },
25
+
26
+ unstyled: unstyledProp,
27
27
  });
28
28
 
29
29
  provide("hideSeparator", props.hideSeparator);
@@ -43,6 +43,9 @@ const onKeydown = (event: KeyboardEvent) => {
43
43
  emit("click");
44
44
  }
45
45
  };
46
+
47
+ const { settings } = useSettings();
48
+ const isUnstyled = settings.global.unstyled || settings.components.card.unstyled || props.unstyled;
46
49
  </script>
47
50
 
48
51
  <template>
@@ -50,9 +53,11 @@ const onKeydown = (event: KeyboardEvent) => {
50
53
  @click="onClick"
51
54
  @keydown="onKeydown"
52
55
  :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
+ :class="`vuetiful-card flex flex-col ${
57
+ isUnstyled ? '' : 'border-token rounded-container-token ring-outline-token'
58
+ } ${background} ${text} ${
59
+ clickable ? `${isUnstyled ? '' : 'card-hover'} hover:cursor-pointer` : ''
60
+ }`"
56
61
  >
57
62
  <slot />
58
63
  </div>
@@ -3,6 +3,24 @@ import { describe, expect, test } from "vitest";
3
3
  import { VCard, VCardBody } from "..";
4
4
 
5
5
  describe("VCardBody", () => {
6
+ test("unstyled", () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `
9
+ <v-card>
10
+ <v-card-body unstyled>John Duck</v-card-body>
11
+ </v-card>
12
+ `,
13
+ components: {
14
+ "v-card": VCard,
15
+ "v-card-body": VCardBody,
16
+ },
17
+ });
18
+
19
+ const content = wrapper.find("[data-test='vuetiful-card-body-content']");
20
+ expect(content.text()).toEqual("John Duck");
21
+ expect(content.classes()).not.toContain("p-4");
22
+ });
23
+
6
24
  test("defaults", async () => {
7
25
  const wrapper = mount({
8
26
  template: /*html*/ `
@@ -1,5 +1,20 @@
1
+ <script setup lang="ts">
2
+ import { unstyledProp } from "@/props";
3
+ import { useSettings } from "@/services";
4
+
5
+ const props = defineProps({
6
+ unstyled: unstyledProp,
7
+ });
8
+
9
+ const { settings } = useSettings();
10
+ const isUnstyled =
11
+ settings.global.unstyled || settings.components.cardBody.unstyled || props.unstyled;
12
+ </script>
1
13
  <template>
2
- <div data-test="vuetiful-card-body-content" class="vuetiful-card-body p-4">
14
+ <div
15
+ data-test="vuetiful-card-body-content"
16
+ :class="`vuetiful-card-body ${isUnstyled ? '' : 'p-4'}`"
17
+ >
3
18
  <slot />
4
19
  </div>
5
20
  </template>
@@ -3,6 +3,24 @@ import { describe, expect, test } from "vitest";
3
3
  import { VCard, VCardFooter } from "..";
4
4
 
5
5
  describe("VCardFooter", () => {
6
+ test("unstyled", () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `
9
+ <v-card>
10
+ <v-card-footer unstyled>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 content = wrapper.find("[data-test='vuetiful-card-footer-content']");
20
+ expect(content.text()).toEqual("John Duck");
21
+ expect(content.classes()).not.toContain("p-4");
22
+ });
23
+
6
24
  test("defaults", async () => {
7
25
  const wrapper = mount({
8
26
  template: /*html*/ `
@@ -1,11 +1,29 @@
1
1
  <script setup lang="ts">
2
- import { inject } from "vue";
2
+ import { inject, useAttrs } from "vue";
3
+ import { unstyledProp } from "@/props";
4
+ import { useSettings } from "@/services";
5
+
6
+ const props = defineProps({
7
+ classSeparator: {
8
+ type: String as () => string,
9
+ default: "opacity-90",
10
+ },
11
+ unstyled: unstyledProp,
12
+ });
13
+
3
14
  const hideSeparator = inject("hideSeparator", false);
15
+
16
+ const attrs = useAttrs();
17
+ const classAttribute = attrs.class as string;
18
+
19
+ const { settings } = useSettings();
20
+ const isUnstyled =
21
+ settings.global.unstyled || settings.components.cardBody.unstyled || props.unstyled;
4
22
  </script>
5
23
 
6
24
  <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">
25
+ <hr v-if="!hideSeparator" data-test="vuetiful-card-footer-separator" class="divider" :class="classSeparator" />
26
+ <div data-test="vuetiful-card-footer-content" :class="`vuetiful-card-footer ${isUnstyled ? '' : 'p-4'} ${classAttribute}`">
9
27
  <slot />
10
28
  </div>
11
29
  </template>
@@ -3,6 +3,24 @@ import { describe, expect, test } from "vitest";
3
3
  import { VCard, VCardHeader } from "..";
4
4
 
5
5
  describe("VCardHeader", () => {
6
+ test("unstyled", () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `
9
+ <v-card>
10
+ <v-card-header unstyled>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 content = wrapper.find("[data-test='vuetiful-card-header-content']");
20
+ expect(content.text()).toEqual("John Duck");
21
+ expect(content.classes()).not.toContain("p-4");
22
+ });
23
+
6
24
  test("defaults", async () => {
7
25
  const wrapper = mount({
8
26
  template: /*html*/ `
@@ -1,5 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { Ref, computed, inject, ref } from 'vue';
2
+ import { unstyledProp } from "@/props";
3
+ import { useSettings } from "@/services";
4
+ import { Ref, computed, inject, ref, useAttrs } from "vue";
5
+
6
+ const props = defineProps({
7
+ classSeparator: {
8
+ type: String as () => string,
9
+ default: "opacity-90",
10
+ },
11
+ unstyled: unstyledProp,
12
+ });
3
13
 
4
14
  const headerRef = ref() as Ref<HTMLDivElement>;
5
15
 
@@ -7,17 +17,28 @@ const hasImageAsChild = computed(() => {
7
17
  const children = headerRef.value?.children;
8
18
  if (!children) return false;
9
19
  const childrenArray = Array.from(children);
10
- return childrenArray.some((child) => child.tagName === 'IMG');
20
+ return childrenArray.some((child) => child.tagName === "IMG");
11
21
  });
12
22
 
13
- const hideSeparator = inject('hideSeparator', false);
23
+ const hideSeparator = inject("hideSeparator", false);
24
+
25
+ const attrs = useAttrs();
26
+ const classAttribute = attrs.class as string;
27
+
28
+ const { settings } = useSettings();
29
+ const isUnstyled =
30
+ settings.global.unstyled || settings.components.cardHeader.unstyled || props.unstyled;
14
31
  </script>
15
32
 
16
33
  <template>
17
- <div ref="headerRef" data-test='vuetiful-card-header-content' :class="`vuetiful-card-header ${hasImageAsChild ? '' : 'p-4'}`">
34
+ <div
35
+ ref="headerRef"
36
+ data-test="vuetiful-card-header-content"
37
+ :class="`vuetiful-card-header ${hasImageAsChild ? '' : `${isUnstyled ? '' : 'p-4'}`} ${classAttribute}`"
38
+ >
18
39
  <slot />
19
40
  </div>
20
- <hr v-if="!hideSeparator" data-test='vuetiful-card-header-separator' class="opacity-90" />
41
+ <hr v-if="!hideSeparator" data-test="vuetiful-card-header-separator" class="divider" :class="classSeparator" />
21
42
  </template>
22
43
 
23
44
  <style>
@@ -0,0 +1,133 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test, vi } from "vitest";
3
+ import { VCodeBlock } from "../..";
4
+
5
+ const clipboardMock = {
6
+ writeText: vi.fn(),
7
+ };
8
+
9
+ Object.defineProperty(window.navigator, "clipboard", {
10
+ value: clipboardMock,
11
+ writable: true,
12
+ });
13
+
14
+ describe("VCodeBlock", () => {
15
+ test("copy button copies code to clipboard", async () => {
16
+ const mockClipboardDirective = {
17
+ mounted(el: HTMLElement, binding: any) {
18
+ el.addEventListener("click", () => {
19
+ el.dataset.copied = binding.value;
20
+ });
21
+ },
22
+ };
23
+
24
+ const wrapper = mount(VCodeBlock, {
25
+ props: {
26
+ code: "John Duck",
27
+ },
28
+ global: {
29
+ directives: {
30
+ clipboard: mockClipboardDirective,
31
+ },
32
+ },
33
+ });
34
+
35
+ await wrapper.find(".vuetiful-code-block-button").trigger("click");
36
+
37
+ expect(clipboardMock.writeText).toHaveBeenCalledWith("John Duck");
38
+
39
+ await new Promise((resolve) => setTimeout(resolve, 2000));
40
+
41
+ expect(wrapper.emitted("copy")).toBeTruthy();
42
+ });
43
+
44
+ test("renders the component with default props", () => {
45
+ const wrapper = mount(VCodeBlock);
46
+ expect(wrapper.exists()).toBe(true);
47
+ expect(wrapper.text()).toContain("Copy");
48
+ expect(wrapper.find(".vuetiful-code-block-language").text()).toBe("plaintext");
49
+ expect(wrapper.find(".vuetiful-code-block-code").classes()).toContain("language-plaintext");
50
+ });
51
+
52
+ test("renders the component with custom props", () => {
53
+ const customProps = {
54
+ language: "javascript",
55
+ code: "const x = 10;",
56
+ buttonText: "Copy Code",
57
+ buttonCopiedText: "Copied!",
58
+ classButton: "custom-button-class",
59
+ classLanguage: "custom-language-class",
60
+ classCode: "custom-code-class",
61
+ preventOverflow: true,
62
+ };
63
+
64
+ const wrapper = mount(VCodeBlock, {
65
+ props: customProps,
66
+ });
67
+
68
+ expect(wrapper.find(".vuetiful-code-block-button").text()).toContain("Copy Code");
69
+ expect(wrapper.find(".vuetiful-code-block-language").text()).toBe("javascript");
70
+ expect(wrapper.find(".vuetiful-code-block-code").classes()).toContain("language-javascript");
71
+ expect(wrapper.find(".vuetiful-code-block-pre").classes()).toContain("whitespace-pre-wrap");
72
+ });
73
+
74
+ test("renders the component with custom props", () => {
75
+ const customProps = {
76
+ language: "js",
77
+ code: "const x = 10;",
78
+ buttonText: "Copy Code",
79
+ buttonCopiedText: "Copied!",
80
+ classButton: "custom-button-class",
81
+ classLanguage: "custom-language-class",
82
+ classCode: "custom-code-class",
83
+ preventOverflow: true,
84
+ };
85
+
86
+ const wrapper = mount(VCodeBlock, { props: customProps });
87
+
88
+ expect(wrapper.find(".vuetiful-code-block-button").text()).toContain("Copy Code");
89
+ expect(wrapper.find(".vuetiful-code-block-language").text()).toBe("javascript");
90
+ expect(wrapper.find(".vuetiful-code-block-code").classes()).toContain("language-js");
91
+ expect(wrapper.find(".vuetiful-code-block-pre").classes()).toContain("whitespace-pre-wrap");
92
+ });
93
+
94
+ test("renders the component with custom props", () => {
95
+ const customProps = {
96
+ language: "ts",
97
+ code: "const x = 10;",
98
+ buttonText: "Copy Code",
99
+ buttonCopiedText: "Copied!",
100
+ classButton: "custom-button-class",
101
+ classLanguage: "custom-language-class",
102
+ classCode: "custom-code-class",
103
+ preventOverflow: true,
104
+ };
105
+
106
+ const wrapper = mount(VCodeBlock, { props: customProps });
107
+
108
+ expect(wrapper.find(".vuetiful-code-block-button").text()).toContain("Copy Code");
109
+ expect(wrapper.find(".vuetiful-code-block-language").text()).toBe("typescript");
110
+ expect(wrapper.find(".vuetiful-code-block-code").classes()).toContain("language-ts");
111
+ expect(wrapper.find(".vuetiful-code-block-pre").classes()).toContain("whitespace-pre-wrap");
112
+ });
113
+
114
+ test("renders the component with custom props", () => {
115
+ const customProps = {
116
+ language: "sh",
117
+ code: "const x = 10;",
118
+ buttonText: "Copy Code",
119
+ buttonCopiedText: "Copied!",
120
+ classButton: "custom-button-class",
121
+ classLanguage: "custom-language-class",
122
+ classCode: "custom-code-class",
123
+ preventOverflow: true,
124
+ };
125
+
126
+ const wrapper = mount(VCodeBlock, { props: customProps });
127
+
128
+ expect(wrapper.find(".vuetiful-code-block-button").text()).toContain("Copy Code");
129
+ expect(wrapper.find(".vuetiful-code-block-language").text()).toBe("console");
130
+ expect(wrapper.find(".vuetiful-code-block-code").classes()).toContain("language-sh");
131
+ expect(wrapper.find(".vuetiful-code-block-pre").classes()).toContain("whitespace-pre-wrap");
132
+ });
133
+ });
@@ -0,0 +1,120 @@
1
+ <script setup lang="ts">
2
+ import { CssClasses, VButton, useSettings, vClipboard } from "@/index";
3
+ import { unstyledProp } from "@/props";
4
+ import { useHighlight } from "@/services/highlight.service";
5
+ import "highlight.js/styles/github-dark.css";
6
+ import { ref } from "vue";
7
+
8
+ const { highlight } = useHighlight();
9
+
10
+ const props = defineProps({
11
+ language: {
12
+ type: String,
13
+ default: "plaintext",
14
+ },
15
+ code: {
16
+ type: String,
17
+ default: "",
18
+ },
19
+
20
+ preventOverflow: {
21
+ type: Boolean,
22
+ default: false,
23
+ },
24
+
25
+ classHeader: {
26
+ type: String as () => CssClasses,
27
+ default: "",
28
+ },
29
+ classLanguage: {
30
+ type: String as () => CssClasses,
31
+ default: "",
32
+ },
33
+ classPre: {
34
+ type: String as () => CssClasses,
35
+ default: "",
36
+ },
37
+ classCode: {
38
+ type: String as () => CssClasses,
39
+ default: "",
40
+ },
41
+
42
+ classButton: {
43
+ type: String as () => CssClasses,
44
+ default: "",
45
+ },
46
+ buttonText: {
47
+ type: String,
48
+ default: "Copy",
49
+ },
50
+ buttonCopiedText: {
51
+ type: String,
52
+ default: "👍",
53
+ },
54
+
55
+ unstyled: unstyledProp,
56
+ });
57
+
58
+ const emit = defineEmits<{
59
+ (event: "copy"): void;
60
+ }>();
61
+
62
+ const copyState = ref(false);
63
+
64
+ // Allow shorthand alias, but show full text in UI
65
+ function languageFormatter(lang: string): string {
66
+ if (lang === "js") return "javascript";
67
+ if (lang === "ts") return "typescript";
68
+ if (["sh", "bash", "zsh", "shell"].includes(lang)) return "console";
69
+ return lang;
70
+ }
71
+
72
+ function onCopyClick() {
73
+ copyState.value = true;
74
+ setTimeout(() => {
75
+ copyState.value = false;
76
+ }, 2000);
77
+ emit("copy");
78
+ }
79
+
80
+ const { settings } = useSettings();
81
+ const isUnstyled =
82
+ settings.global.unstyled || settings.components.codeBlock.unstyled || props.unstyled;
83
+ </script>
84
+
85
+ <template v-if="language && code">
86
+ <div
87
+ :class="`vuetiful-code-block code-block ${
88
+ isUnstyled
89
+ ? ''
90
+ : 'max-w-full bg-[#171717] text-sm text-white shadow rounded-container-token'
91
+ }`"
92
+ >
93
+ <header
94
+ :class="`vuetiful-code-block-header ${
95
+ isUnstyled
96
+ ? ''
97
+ : 'flex items-center justify-between p-2 pb-0 pl-4 text-xs uppercase text-[#a4a4a4]'
98
+ } ${classHeader}`"
99
+ >
100
+ <span :class="`vuetiful-code-block-language ${classLanguage}`">{{
101
+ languageFormatter(language)
102
+ }}</span>
103
+ <v-button
104
+ size="sm"
105
+ :class="`vuetiful-code-block-button ${
106
+ classButton ? classButton : 'bg-[#171717] dark:bg-[#171717] text-[#a4a4a4] dark:text-[#a4a4a4]'
107
+ }`"
108
+ @click="onCopyClick()"
109
+ v-clipboard="code"
110
+ >
111
+ {{ !copyState ? buttonText : buttonCopiedText }}
112
+ </v-button>
113
+ </header>
114
+ <pre
115
+ :class="`vuetiful-code-block-pre ${
116
+ isUnstyled ? '' : '!rounded-t-none bg-transparent p-4 !pt-0'
117
+ } ${preventOverflow ? 'whitespace-pre-wrap break-all' : 'overflow-auto'} ${classPre}`"
118
+ ><code :class="`vuetiful-code-block-code language-${language} ${classCode}`" v-html="highlight(code, language)"></code></pre>
119
+ </div>
120
+ </template>
@@ -9,18 +9,22 @@ describe("VListbox", () => {
9
9
  const wrapper = mount(VListbox);
10
10
 
11
11
  expect(wrapper.props()).toEqual({
12
+ modelValue: undefined,
13
+ classLabel: "",
14
+ textButton: "Select an option",
15
+ classButton: "",
16
+ classItem: "",
17
+ classItems: "",
18
+ horizontal: false,
19
+ multiple: false,
12
20
  active: "variant-filled",
21
+ hover: "hover:variant-ghost",
13
22
  background: "bg-surface-200-700-token",
14
- buttonText: "Select an option",
23
+ text: "text-surface-900 dark:text-surface-50",
24
+ unstyled: false,
15
25
  by: undefined,
16
26
  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",
27
+ textLabel: undefined,
24
28
  });
25
29
  });
26
30
 
@@ -44,8 +48,8 @@ describe("VListbox", () => {
44
48
  },
45
49
  });
46
50
 
47
- const listbox = wrapper.find("[data-test='v-listbox']");
48
- await listbox.find("[data-test='v-listbox-button']").trigger("click");
51
+ const listbox = wrapper.find("[data-test='listbox']");
52
+ await listbox.find("[data-test='listbox-button']").trigger("click");
49
53
  await listbox.find("[data-test='vuetiful']").trigger("click");
50
54
  expect(choice.value).toBe("vuetiful");
51
55
  });
@@ -71,8 +75,8 @@ describe("VListbox", () => {
71
75
  },
72
76
  });
73
77
 
74
- const listbox = wrapper.find("[data-test='v-listbox']");
75
- const button = listbox.find("[data-test='v-listbox-button']");
78
+ const listbox = wrapper.find("[data-test='listbox']");
79
+ const button = listbox.find("[data-test='listbox-button']");
76
80
  expect(button.text()).toBe("Select an option");
77
81
  await button.trigger("click");
78
82
  await listbox.find("[data-test='vuetiful']").trigger("click");
@@ -102,8 +106,8 @@ describe("VListbox", () => {
102
106
  },
103
107
  });
104
108
 
105
- const listbox = wrapper.find("[data-test='v-listbox']");
106
- const button = listbox.find("[data-test='v-listbox-button']");
109
+ const listbox = wrapper.find("[data-test='listbox']");
110
+ const button = listbox.find("[data-test='listbox-button']");
107
111
  expect(button.text()).toBe("Select an option");
108
112
 
109
113
  await button.trigger("click");
@@ -116,4 +120,27 @@ describe("VListbox", () => {
116
120
  expect(button.text()).toBe("2 options selected");
117
121
  });
118
122
  });
119
- });
123
+
124
+ describe("unstyled", () => {
125
+ test("should only have vuetiful- classes", async () => {
126
+ const wrapper = mount(VListbox, {
127
+ props: {
128
+ unstyled: true,
129
+ },
130
+ });
131
+
132
+ const listbox = wrapper.find("[data-test='listbox']");
133
+ await listbox.find("[data-test='listbox-button']").trigger("click");
134
+ const listboxItems = wrapper.find("[data-test='listbox-items']");
135
+
136
+ expect(listbox.classes()).toEqual(["vuetiful-listbox"]);
137
+ expect(listboxItems.classes()).toEqual([
138
+ "z-10",
139
+ "bg-surface-200-700-token",
140
+ "text-surface-900",
141
+ "dark:text-surface-50",
142
+ "flex-col",
143
+ ]);
144
+ });
145
+ });
146
+ });