@code-coaching/vuetiful 0.18.1 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-coaching/vuetiful",
3
- "version": "0.18.1",
3
+ "version": "0.20.0",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "onchange 'src/**/*.vue' 'src/**/*.ts' 'src/**/*.css' -- npm run build",
@@ -0,0 +1,22 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import { VAccordion } from "..";
4
+
5
+ describe("VAccordion", () => {
6
+ test("defaults", async () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `<v-accordion></v-accordion>`,
9
+ components: {
10
+ "v-accordion": VAccordion,
11
+ },
12
+ });
13
+
14
+ expect(wrapper.classes()).toEqual([
15
+ "vuetiful-accordion",
16
+ "flex",
17
+ "w-full",
18
+ "flex-col",
19
+ "gap-1",
20
+ ]);
21
+ });
22
+ });
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import { provide } from 'vue';
3
+
4
+ const props = defineProps({
5
+ hover: {
6
+ type: String,
7
+ default: 'hover:variant-soft',
8
+ },
9
+ background: {
10
+ type: String,
11
+ default: 'bg-surface-200-700-token',
12
+ },
13
+ });
14
+
15
+ provide('hover', props.hover);
16
+ provide('background', props.background);
17
+ </script>
18
+
19
+ <template>
20
+ <div class="vuetiful-accordion flex w-full flex-col gap-1">
21
+ <slot></slot>
22
+ </div>
23
+ </template>
@@ -0,0 +1,85 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import { VAccordion, VAccordionItem } from "..";
4
+
5
+ describe("VAccordionItem", () => {
6
+ test("defaults", async () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `
9
+ <v-accordion>
10
+ <v-accordion-item title="Vuetiful">John Duck</v-accordion-item>
11
+ <v-accordion-item title="Is">Janine Duck</v-accordion-item>
12
+ </v-accordion>`,
13
+ components: {
14
+ "v-accordion": VAccordion,
15
+ "v-accordion-item": VAccordionItem,
16
+ },
17
+ });
18
+
19
+ const accordionItems = wrapper.findAllComponents(VAccordionItem);
20
+ accordionItems.forEach((accordionItem) => {
21
+ expect(accordionItem.classes()).toEqual(["vuetiful-accordion-item"]);
22
+
23
+ const accordionItemButton = accordionItem.find(".vuetiful-accordion-item-button");
24
+ expect(accordionItemButton.classes()).toEqual([
25
+ "bg-surface-200-700-token",
26
+ "hover:variant-soft",
27
+ "vuetiful-accordion-item-button",
28
+ "w-full",
29
+ "rounded-container-token",
30
+ ]);
31
+
32
+ const accordionItemTitle = accordionItem.find(".vuetiful-accordion-title");
33
+ expect(accordionItemTitle.text()).toEqual(accordionItem.props().title);
34
+ });
35
+ });
36
+
37
+ test("open", async () => {
38
+ const wrapper = mount({
39
+ template: /*html*/ `
40
+ <v-accordion>
41
+ <v-accordion-item title="Vuetiful" open>John Duck</v-accordion-item>
42
+ <v-accordion-item title="Is">Janine Duck</v-accordion-item>
43
+ </v-accordion>`,
44
+ components: {
45
+ "v-accordion": VAccordion,
46
+ "v-accordion-item": VAccordionItem,
47
+ },
48
+ });
49
+
50
+ const accordionItems = wrapper.findAllComponents(VAccordionItem);
51
+ const accordionItemButtonOne = accordionItems[0].find(".vuetiful-accordion-item-button");
52
+ const accordionItemButtonTwo = accordionItems[1].find(".vuetiful-accordion-item-button");
53
+
54
+ await accordionItemButtonOne.trigger("click");
55
+
56
+ expect(accordionItemButtonOne.classes()).toEqual([
57
+ "bg-surface-200-700-token",
58
+ "hover:variant-soft",
59
+ "!rounded-bl-none",
60
+ "!rounded-br-none",
61
+ "vuetiful-accordion-item-button",
62
+ "w-full",
63
+ "rounded-container-token",
64
+ ]);
65
+ expect(accordionItemButtonTwo.classes()).toEqual([
66
+ "bg-surface-200-700-token",
67
+ "hover:variant-soft",
68
+ "vuetiful-accordion-item-button",
69
+ "w-full",
70
+ "rounded-container-token",
71
+ ]);
72
+
73
+ const accordionItemPanelOne = accordionItems[0].find(".vuetiful-accordion-item-panel");
74
+
75
+ expect(accordionItemPanelOne.classes()).toEqual([
76
+ "vuetiful-accordion-item-panel",
77
+ "p-4",
78
+ "pt-0",
79
+ "rounded-container-token",
80
+ "bg-surface-200-700-token",
81
+ "!rounded-tl-none",
82
+ "!rounded-tr-none",
83
+ ]);
84
+ });
85
+ });
@@ -0,0 +1,60 @@
1
+ <script setup lang="ts">
2
+ import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue";
3
+ import { inject } from "vue";
4
+
5
+ defineProps({
6
+ title: {
7
+ type: String,
8
+ required: true,
9
+ },
10
+ });
11
+
12
+ const hover = inject("hover");
13
+ const background = inject("background");
14
+ </script>
15
+
16
+ <template>
17
+ <Disclosure class="vuetiful-accordion-item" as="div" v-slot="{ open }">
18
+ <DisclosureButton
19
+ :class="`${background} ${hover} ${open ? '!rounded-bl-none !rounded-br-none' : ''}`"
20
+ class="vuetiful-accordion-item-button w-full rounded-container-token"
21
+ >
22
+ <div
23
+ class="flex items-center justify-between p-4 rounded-container-token hover:cursor-pointer"
24
+ :class="`${background} ${hover} ${open ? '!rounded-bl-none !rounded-br-none' : ''}`"
25
+ >
26
+ <span class="vuetiful-accordion-title">{{ title }}</span>
27
+ <slot v-if="!open" name="open-item">
28
+ <!-- https://fontawesome.com/icons/plus?f=classic&s=solid -->
29
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
30
+ <!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
31
+ <path
32
+ d="M256 80c0-17.7-14.3-32-32-32s-32 14.3-32 32V224H48c-17.7 0-32 14.3-32 32s14.3 32 32 32H192V432c0 17.7 14.3 32 32 32s32-14.3 32-32V288H400c17.7 0 32-14.3 32-32s-14.3-32-32-32H256V80z"
33
+ />
34
+ </svg>
35
+ </slot>
36
+ <slot v-if="open" name="close-item">
37
+ <!-- https://fontawesome.com/icons/minus?f=classic&s=solid -->
38
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
39
+ <!--! Font Awesome Free 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
40
+ <path
41
+ d="M432 256c0 17.7-14.3 32-32 32L48 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l352 0c17.7 0 32 14.3 32 32z"
42
+ />
43
+ </svg>
44
+ </slot>
45
+ </div>
46
+ </DisclosureButton>
47
+ <DisclosurePanel
48
+ class="vuetiful-accordion-item-panel p-4 pt-0 rounded-container-token"
49
+ :class="`${open ? `${background} !rounded-tl-none !rounded-tr-none` : ''}`"
50
+ >
51
+ <slot></slot>
52
+ </DisclosurePanel>
53
+ </Disclosure>
54
+ </template>
55
+
56
+ <style scoped>
57
+ .icon {
58
+ @apply my-1 h-4 w-4 fill-current;
59
+ }
60
+ </style>
@@ -0,0 +1,90 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import { VAlert } from "..";
4
+
5
+ describe("VAlert", () => {
6
+ test("types", () => {
7
+ const wrapper = mount({
8
+ template: /*html*/ `
9
+ <v-alert data-test="default"></v-alert>
10
+ <v-alert data-test="info" type="info"></v-alert>
11
+ <v-alert data-test="success" type="success"></v-alert>
12
+ <v-alert data-test="warning" type="warning"></v-alert>
13
+ <v-alert data-test="error" type="error"></v-alert>
14
+ `,
15
+ components: {
16
+ "v-alert": VAlert,
17
+ },
18
+ });
19
+
20
+ const defaultAlert = wrapper.find("[data-test=default]");
21
+ expect(defaultAlert.classes()).toContain("variant-filled-primary");
22
+
23
+ const infoAlert = wrapper.find("[data-test=info]");
24
+ expect(infoAlert.classes()).toContain("variant-filled");
25
+
26
+ const successAlert = wrapper.find("[data-test=success]");
27
+ expect(successAlert.classes()).toContain("variant-filled-success");
28
+
29
+ const warningAlert = wrapper.find("[data-test=warning]");
30
+ expect(warningAlert.classes()).toContain("variant-filled-warning");
31
+ });
32
+
33
+ describe("given close icon is clicked", () => {
34
+ test("should emit close", async () => {
35
+ const wrapper = mount(VAlert);
36
+ await wrapper.find("[data-test=close]").trigger("click");
37
+ expect(wrapper.emitted()["close"][0]).toEqual([]);
38
+
39
+ await wrapper.find("[data-test=close]").trigger("keydown", { key: "Enter" });
40
+ expect(wrapper.emitted()["close"][0]).toEqual([]);
41
+
42
+ await wrapper.find("[data-test=close]").trigger("keydown", { key: " " });
43
+ expect(wrapper.emitted()["close"][0]).toEqual([]);
44
+ });
45
+ });
46
+
47
+ describe("given a pre slot is provided", () => {
48
+ test("should render the pre slot", () => {
49
+ const wrapper = mount(VAlert, {
50
+ slots: {
51
+ pre: "John Duck",
52
+ },
53
+ });
54
+ expect(wrapper.text()).toContain("John Duck");
55
+ });
56
+ })
57
+
58
+ describe("given a actions slot is provided", () => {
59
+ test("should render the actions slot", () => {
60
+ const wrapper = mount(VAlert, {
61
+ slots: {
62
+ actions: "John Duck",
63
+ },
64
+ });
65
+ expect(wrapper.text()).toContain("John Duck");
66
+ });
67
+ })
68
+
69
+ describe("given hide-icon prop is present", () => {
70
+ test("should not render an icon", () => {
71
+ const wrapper = mount(VAlert, {
72
+ props: {
73
+ hideIcon: true,
74
+ },
75
+ });
76
+ expect(wrapper.findAll(".icon").length).toBe(1);
77
+ });
78
+ })
79
+
80
+ describe("given hide-close prop is present", () => {
81
+ test("should not render a close icon", () => {
82
+ const wrapper = mount(VAlert, {
83
+ props: {
84
+ hideClose: true,
85
+ },
86
+ });
87
+ expect(wrapper.find("[data-test=close]").exists()).toBe(false);
88
+ });
89
+ })
90
+ });
@@ -0,0 +1,133 @@
1
+ <script setup lang="ts">
2
+ import { PropType, computed } from "vue";
3
+
4
+ const emit = defineEmits(["close"]);
5
+ const props = defineProps({
6
+ hideIcon: {
7
+ type: Boolean,
8
+ default: false,
9
+ },
10
+ hideClose: {
11
+ type: Boolean,
12
+ default: false,
13
+ },
14
+
15
+ show: {
16
+ type: Boolean,
17
+ default: true,
18
+ },
19
+ type: {
20
+ type: String as PropType<"info" | "success" | "warning" | "error">,
21
+ default: "",
22
+ },
23
+ });
24
+
25
+ const typeBackground = computed(() => {
26
+ switch (props.type) {
27
+ case "info":
28
+ return "variant-filled";
29
+ case "success":
30
+ return "variant-filled-success";
31
+ case "warning":
32
+ return "variant-filled-warning";
33
+ case "error":
34
+ return "variant-filled-error";
35
+ default:
36
+ return "variant-filled-primary";
37
+ }
38
+ });
39
+
40
+ const close = () => emit("close");
41
+ const handleKeydown = (event: KeyboardEvent) => {
42
+ if (event.key === "Enter" || event.key === " ") {
43
+ close();
44
+ }
45
+ };
46
+ </script>
47
+
48
+ <template>
49
+ <aside
50
+ v-if="show"
51
+ :class="`vuetiful-alert flex w-full flex-row items-center gap-4 p-4 border-token rounded-container-token ${typeBackground}`"
52
+ >
53
+ <div v-if="!hideIcon">
54
+ <slot v-if="$slots.pre" name="pre" />
55
+ <template v-if="!$slots.pre">
56
+ <!-- https://fontawesome.com/icons/circle-info?f=classic&s=solid -->
57
+ <svg
58
+ v-if="type === 'info'"
59
+ class="icon"
60
+ xmlns="http://www.w3.org/2000/svg"
61
+ viewBox="0 0 512 512"
62
+ >
63
+ <path
64
+ d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"
65
+ />
66
+ </svg>
67
+
68
+ <!-- https://fontawesome.com/icons/circle-check?f=classic&s=solid -->
69
+ <svg
70
+ v-if="type === 'success'"
71
+ class="icon"
72
+ xmlns="http://www.w3.org/2000/svg"
73
+ viewBox="0 0 512 512"
74
+ >
75
+ <path
76
+ d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"
77
+ />
78
+ </svg>
79
+
80
+ <!-- https://fontawesome.com/icons/circle-exclamation?f=classic&s=solid -->
81
+ <svg
82
+ v-if="type === 'warning'"
83
+ class="icon"
84
+ xmlns="http://www.w3.org/2000/svg"
85
+ viewBox="0 0 512 512"
86
+ >
87
+ <path
88
+ d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"
89
+ />
90
+ </svg>
91
+
92
+ <!-- https://fontawesome.com/icons/triangle-exclamation?f=classic&s=solid -->
93
+ <svg
94
+ v-if="type === 'error'"
95
+ class="icon"
96
+ xmlns="http://www.w3.org/2000/svg"
97
+ viewBox="0 0 512 512"
98
+ >
99
+ <path
100
+ d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"
101
+ />
102
+ </svg>
103
+ </template>
104
+ </div>
105
+
106
+ <div class="vuetiful-alert-message flex-auto">
107
+ <slot />
108
+ </div>
109
+
110
+ <slot name="actions"> </slot>
111
+ <!-- https://fontawesome.com/icons/xmark?f=classic&s=solid -->
112
+ <svg
113
+ data-test="close"
114
+ v-if="!hideClose"
115
+ tabindex="0"
116
+ @keydown="handleKeydown"
117
+ @click="close"
118
+ class="icon hover:cursor-pointer"
119
+ xmlns="http://www.w3.org/2000/svg"
120
+ viewBox="0 0 384 512"
121
+ >
122
+ <path
123
+ d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
124
+ />
125
+ </svg>
126
+ </aside>
127
+ </template>
128
+
129
+ <style scoped>
130
+ .icon {
131
+ @apply my-1 h-6 min-h-[1.5rem] w-6 min-w-[1.5rem] fill-current;
132
+ }
133
+ </style>
@@ -14,7 +14,15 @@ import VTab from "./VTabs/VTab.vue";
14
14
  import VTabPanel from "./VTabs/VTabPanel.vue";
15
15
  import VTabs from "./VTabs/VTabs.vue";
16
16
 
17
+ import VAccordion from "./VAccordion/VAccordion.vue";
18
+ import VAccordionItem from "./VAccordion/VAccordionItem.vue";
19
+
20
+ import VAlert from "./VAlert.vue";
21
+
17
22
  export {
23
+ VAccordion,
24
+ VAccordionItem,
25
+ VAlert,
18
26
  VDrawer,
19
27
  VListbox,
20
28
  VListboxButton,