@code-coaching/vuetiful 0.15.2 → 0.16.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.15.2",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "onchange 'src/**/*.vue' 'src/**/*.ts' 'src/**/*.css' -- npm run build",
@@ -0,0 +1,14 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test } from "vitest";
3
+ import { VCodeBlock } from ".";
4
+
5
+ test("VCodeBlock", () => {
6
+ expect(VCodeBlock).toBeTruthy();
7
+ });
8
+
9
+ // TODO: add tests
10
+ test("VCodeBlock using slot", () => {
11
+ const wrapper = mount(VCodeBlock);
12
+
13
+ expect(wrapper).toBeTruthy();
14
+ });
@@ -0,0 +1,20 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test, vi } from "vitest";
3
+ import { VLightSwitch } from ".";
4
+ import { useDarkMode } from "@/services";
5
+
6
+ const { MODE } = useDarkMode();
7
+
8
+ const matchMediaMock = (matches: boolean) => vi.fn(() => ({ matches, onchange: vi.fn() }));
9
+
10
+ test("VLightSwitch", () => {
11
+ expect(VLightSwitch).toBeTruthy();
12
+ });
13
+
14
+ // TODO: add tests
15
+ test("VLightSwitch using slot", () => {
16
+ window.matchMedia = matchMediaMock(MODE.LIGHT) as any;
17
+ const wrapper = mount(VLightSwitch);
18
+
19
+ expect(wrapper).toBeTruthy();
20
+ });
@@ -9,14 +9,14 @@ defineProps({
9
9
  },
10
10
  });
11
11
 
12
- const active = (inject("active") as string);
13
- const hover = (inject("hover") as string);
12
+ const active = inject("active") as string;
13
+ const hover = inject("hover") as string;
14
14
  </script>
15
15
 
16
16
  <template>
17
17
  <RadioGroupOption v-slot="{ checked, disabled }" :value="value">
18
18
  <div
19
- data-test="radio-item"
19
+ data-test="radio-item"
20
20
  :class="`radio-item px-4 py-1 text-center text-base rounded-token ${
21
21
  checked ? active : hover
22
22
  } ${disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'}`"
@@ -0,0 +1,14 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test } from "vitest";
3
+ import { VDrawer } from ".";
4
+
5
+ test("VDrawer", () => {
6
+ expect(VDrawer).toBeTruthy();
7
+ });
8
+
9
+ // TODO: add tests
10
+ test("VDrawer using slot", () => {
11
+ const wrapper = mount(VDrawer);
12
+
13
+ expect(wrapper).toBeTruthy();
14
+ });
@@ -0,0 +1,93 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, expect, test } from "vitest";
3
+ import { VPreview } from ".";
4
+
5
+ test("VPreview", () => {
6
+ expect(VPreview).toBeTruthy();
7
+ });
8
+
9
+ describe("VPreview", () => {
10
+ test("defaults", () => {
11
+ const wrapper = mount(VPreview);
12
+
13
+ expect(wrapper.props()).toEqual({
14
+ background: "neutral",
15
+ hideSwatches: false,
16
+ regionFooter: "",
17
+ regionHeader: "",
18
+ regionPreview: "",
19
+ regionSource: "",
20
+ regionViewport: "",
21
+ hideMobileToggle: false,
22
+ });
23
+ });
24
+
25
+ describe("given code radio item is selected", () => {
26
+ test("renders source code", async () => {
27
+ const wrapper = mount(VPreview, {
28
+ props: {
29
+ regionSource: "source",
30
+ },
31
+ });
32
+
33
+ expect(wrapper.find("[data-test='previewer-source']").exists()).toBe(false);
34
+ expect(wrapper.find("[data-test='previewer-preview']").exists()).toBe(true);
35
+
36
+ const radioItemCode = wrapper.find("[data-test='radio-item-code']");
37
+ await radioItemCode.trigger("click");
38
+
39
+ expect(wrapper.find("[data-test='previewer-source']").exists()).toBe(true);
40
+ expect(wrapper.find("[data-test='previewer-preview']").exists()).toBe(false);
41
+ });
42
+ });
43
+
44
+ describe("given the swatch button is clicked", () => {
45
+ test("renders swatches", async () => {
46
+ const wrapper = mount(VPreview);
47
+
48
+ expect(wrapper.find("[data-test='swatches']").exists()).toBe(false);
49
+
50
+ const swatchButton = wrapper.find("[data-test='swatch-button']");
51
+ await swatchButton.trigger("click");
52
+
53
+ expect(wrapper.find("[data-test='swatches']").exists()).toBe(true);
54
+ });
55
+ });
56
+
57
+ describe("given the mobile toggle button is clicked", () => {
58
+ test("renders mobile preview", async () => {
59
+ const wrapper = mount(VPreview);
60
+
61
+ const before = wrapper.find("[data-test='previewer-preview']");
62
+ expect(before.classes()).not.toContain("mobile-screen");
63
+ expect(before.classes()).toContain("w-full");
64
+
65
+ const radioItemMobile = wrapper.find("[data-test='radio-item-mobile']");
66
+ await radioItemMobile.trigger("click");
67
+
68
+ const after = wrapper.find("[data-test='previewer-preview']");
69
+ expect(after.classes()).toContain("mobile-screen");
70
+ expect(after.classes()).not.toContain("w-full");
71
+ });
72
+ })
73
+ });
74
+
75
+ // test("VPreview", () => {
76
+ // const wrapper = mount(VPreview, {
77
+ // slots: {
78
+ // preview: "<div data-test='preview-slot'>Preview</div>",
79
+ // source: "<div data-test='source-slot'>Source</div>",
80
+ // },
81
+ // });
82
+
83
+ // const previewerPreview = wrapper.find("[data-test='previewer-preview']");
84
+ // const previewSlot = previewerPreview.find("[data-test='preview-slot']");
85
+
86
+ // console.log(wrapper.html());
87
+ // const previewerSource = wrapper.find("[data-test='previewer-source']");
88
+ // expect(previewerSource.exists()).toBe(false);
89
+ // const sourceSlot = previewerSource.find("[data-test='source-slot']");
90
+
91
+ // expect(previewSlot.exists()).toBe(true);
92
+ // expect(sourceSlot.exists()).toBe(true);
93
+ // })
@@ -0,0 +1,231 @@
1
+ <script setup lang="ts">
2
+ import { VButton, VRadioGroup, VRadioItem } from "@/index";
3
+ import { ref } from "vue";
4
+
5
+ const backgrounds: Record<string, string> = {
6
+ "bg-transparent": "bg-transparent",
7
+ neutral: "bg-white/25 dark:bg-black/25",
8
+ "neutral-opaque": "bg-white/50 dark:bg-black/50",
9
+
10
+ "variant-filled-surface": "variant-filled-surface",
11
+ "variant-filled-primary": "variant-filled-primary",
12
+ "variant-filled-secondary": "variant-filled-secondary",
13
+ "variant-filled-tertiary": "variant-filled-tertiary",
14
+ "variant-filled-success": "variant-filled-success",
15
+ "variant-filled-warning": "variant-filled-warning",
16
+ "variant-filled-error": "variant-filled-error",
17
+
18
+ // TODO: implement gradient backgrounds
19
+ // 'primary-to-secondary': 'bg-gradient-to-br variant-gradient-primary-secondary',
20
+ // 'secondary-to-tertiary': 'bg-gradient-to-br variant-gradient-secondary-tertiary',
21
+ // 'tertiary-to-primary': 'bg-gradient-to-br variant-gradient-tertiary-primary',
22
+ // 'secondary-to-primary': 'bg-gradient-to-br variant-gradient-secondary-primary',
23
+ // 'tertiary-to-secondary': 'bg-gradient-to-br variant-gradient-tertiary-secondary',
24
+ // 'primary-to-tertiary': 'bg-gradient-to-br variant-gradient-primary-tertiary',
25
+ // 'success-to-warning': 'bg-gradient-to-br variant-gradient-success-warning',
26
+ // 'warning-to-error': 'bg-gradient-to-br variant-gradient-warning-error',
27
+ // 'error-to-success': 'bg-gradient-to-br variant-gradient-error-success',
28
+ // 'warning-to-success': 'bg-gradient-to-br variant-gradient-warning-success',
29
+ // 'error-to-warning': 'bg-gradient-to-br variant-gradient-error-warning',
30
+ // 'success-to-error': 'bg-gradient-to-br variant-gradient-success-error',
31
+ };
32
+
33
+ const props = defineProps({
34
+ hideMobileToggle: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ hideSwatches: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
42
+ background: {
43
+ type: String,
44
+ default: "neutral",
45
+ },
46
+
47
+ // Props (regions)
48
+ regionHeader: {
49
+ type: String,
50
+ default: "",
51
+ },
52
+ regionViewport: {
53
+ type: String,
54
+ default: "",
55
+ },
56
+ regionPreview: {
57
+ type: String,
58
+ default: "",
59
+ },
60
+ regionFooter: {
61
+ type: String,
62
+ default: "",
63
+ },
64
+ regionSource: {
65
+ type: String,
66
+ default: "",
67
+ },
68
+ });
69
+
70
+ const chosenBackground = ref(props.background);
71
+ const tabView = ref("preview");
72
+ const radioSize = ref("full");
73
+ const swatches = ref(false);
74
+
75
+ const toggleSwatches = () => (swatches.value = !swatches.value);
76
+ </script>
77
+
78
+ <template>
79
+ <div
80
+ class="vuetiful-previewer overflow-hidden shadow-2xl shadow-surface-500/10 rounded-container-token dark:shadow-black/10"
81
+ >
82
+ <header
83
+ :class="`vuetiful-previewer-header flex items-center justify-between gap-4 p-4 bg-surface-200-700-token ${regionHeader}`"
84
+ >
85
+ <div class="flex items-center gap-2">
86
+ <v-radio-group v-model="tabView">
87
+ <v-radio-item data-test="radio-item-preview" value="preview" title="Preview">
88
+ <slot name="preview-item">
89
+ <!-- https://fontawesome.com/icons/eye?f=classic&s=solid -->
90
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
91
+ <path
92
+ d="M288 32c-80.8 0-145.5 36.8-192.6 80.6C48.6 156 17.3 208 2.5 243.7c-3.3 7.9-3.3 16.7 0 24.6C17.3 304 48.6 356 95.4 399.4C142.5 443.2 207.2 480 288 480s145.5-36.8 192.6-80.6c46.8-43.5 78.1-95.4 93-131.1c3.3-7.9 3.3-16.7 0-24.6c-14.9-35.7-46.2-87.7-93-131.1C433.5 68.8 368.8 32 288 32zM144 256a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm144-64c0 35.3-28.7 64-64 64c-7.1 0-13.9-1.2-20.3-3.3c-5.5-1.8-11.9 1.6-11.7 7.4c.3 6.9 1.3 13.8 3.2 20.7c13.7 51.2 66.4 81.6 117.6 67.9s81.6-66.4 67.9-117.6c-11.1-41.5-47.8-69.4-88.6-71.1c-5.8-.2-9.2 6.1-7.4 11.7c2.1 6.4 3.3 13.2 3.3 20.3z"
93
+ />
94
+ </svg>
95
+ </slot>
96
+ </v-radio-item>
97
+ <v-radio-item data-test="radio-item-code" value="code" title="Code">
98
+ <slot name="code-item">
99
+ <!-- https://fontawesome.com/icons/code?f=classic&s=solid -->
100
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512">
101
+ <path
102
+ d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"
103
+ />
104
+ </svg>
105
+ </slot>
106
+ </v-radio-item>
107
+ </v-radio-group>
108
+ <v-radio-group
109
+ class="hidden sm:!inline-flex"
110
+ v-if="!hideMobileToggle && tabView === 'preview'"
111
+ v-model="radioSize"
112
+ >
113
+ <v-radio-item data-test="radio-item-mobile" value="mobile">
114
+ <slot name="mobile-item">
115
+ <!-- https://fontawesome.com/icons/mobile-screen?f=classic&s=solid -->
116
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
117
+ <path
118
+ d="M16 64C16 28.7 44.7 0 80 0H304c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H80c-35.3 0-64-28.7-64-64V64zM144 448c0 8.8 7.2 16 16 16h64c8.8 0 16-7.2 16-16s-7.2-16-16-16H160c-8.8 0-16 7.2-16 16zM304 64H80V384H304V64z"
119
+ />
120
+ </svg>
121
+ </slot>
122
+ </v-radio-item>
123
+ <v-radio-item value="full">
124
+ <slot name="desktop-item">
125
+ <!-- https://fontawesome.com/icons/display?f=classic&s=solid -->
126
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
127
+ <path
128
+ d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V352H64V64H512z"
129
+ />
130
+ </svg>
131
+ </slot>
132
+ </v-radio-item>
133
+ </v-radio-group>
134
+ </div>
135
+ <v-button
136
+ v-if="!hideSwatches"
137
+ data-test="swatch-button"
138
+ :class="`${
139
+ swatches ? 'variant-filled' : 'variant-ghost'
140
+ } px-4 py-1 border-token border-surface-400-500-token`"
141
+ @click="toggleSwatches"
142
+ title="Backgrounds"
143
+ >
144
+ <slot name="swatchbook-item">
145
+ <!-- https://fontawesome.com/icons/swatchbook?f=classic&s=solid -->
146
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
147
+ <path
148
+ d="M0 32C0 14.3 14.3 0 32 0H160c17.7 0 32 14.3 32 32V416c0 53-43 96-96 96s-96-43-96-96V32zM223.6 425.9c.3-3.3 .4-6.6 .4-9.9V154l75.4-75.4c12.5-12.5 32.8-12.5 45.3 0l90.5 90.5c12.5 12.5 12.5 32.8 0 45.3L223.6 425.9zM182.8 512l192-192H480c17.7 0 32 14.3 32 32V480c0 17.7-14.3 32-32 32H182.8zM128 64H64v64h64V64zM64 192v64h64V192H64zM96 440a24 24 0 1 0 0-48 24 24 0 1 0 0 48z"
149
+ />
150
+ </svg>
151
+ </slot>
152
+ </v-button>
153
+ </header>
154
+
155
+ <!-- There is some odd behavior with test coverge, v-model must be the last property in this component -->
156
+ <v-radio-group
157
+ v-if="swatches"
158
+ data-test="swatches"
159
+ :class="`vuetiful-previewer-swatches variant-soft grid grid-cols-6 gap-2 !rounded-none p-4 sm:grid-cols-12`"
160
+ active="bg-transparent"
161
+ hover="bg-transparent"
162
+ v-model="chosenBackground"
163
+ >
164
+ <v-radio-item
165
+ v-for="([key, value], index) in Object.entries(backgrounds)"
166
+ :value="key"
167
+ :key="index"
168
+ :class="`${value} flex aspect-square cursor-pointer items-center justify-center rounded ring-[1px] ring-surface-500/50`"
169
+ :title="key"
170
+ >
171
+ <slot v-if="index === 0 && chosenBackground !== key" name="cancel-item">
172
+ <!-- https://fontawesome.com/icons/circle-xmark?f=classic&s=solid -->
173
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
174
+ <path
175
+ d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"
176
+ />
177
+ </svg>
178
+ </slot>
179
+
180
+ <slot v-if="chosenBackground === key" name="selected-item">
181
+ <!-- https://fontawesome.com/icons/circle-check?f=classic&s=solid -->
182
+ <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
183
+ <path
184
+ 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"
185
+ />
186
+ </svg>
187
+ </slot>
188
+ </v-radio-item>
189
+ </v-radio-group>
190
+
191
+ <template v-if="tabView === 'preview'">
192
+ <div
193
+ :class="`vuetiful-previewer-viewport p-4 md:p-10 ${backgrounds[chosenBackground]} ${regionViewport}`"
194
+ >
195
+ <!-- <div v-if="$slots.lead" class="vuetiful-previewer-lead"><slot name="lead" /></div> -->
196
+ <div
197
+ data-test="previewer-preview"
198
+ :class="`vuetiful-previewer-preview mx-auto flex duration-300 ${
199
+ radioSize === 'mobile'
200
+ ? 'mobile-screen flex h-[812px] max-w-[375px] flex-col overflow-hidden rounded-3xl border-8 border-black/50 p-2 shadow-2xl dark:border-white/50'
201
+ : 'w-full items-center justify-center'
202
+ } ${regionPreview}`"
203
+ >
204
+ <slot name="preview">(preview)</slot>
205
+ </div>
206
+ <!-- <div v-if="$slots.trail" class="vuetiful-previewer-trail"><slot name="trail" /></div> -->
207
+ </div>
208
+ <footer
209
+ v-if="$slots.footer"
210
+ :class="`vuetiful-previewer-footer variant-soft p-4 ${regionFooter}`"
211
+ >
212
+ <slot name="footer" />
213
+ </footer>
214
+ </template>
215
+
216
+ <template v-if="tabView === 'code'">
217
+ <div
218
+ data-test="previewer-source"
219
+ :class="`vuetiful-previewer-source space-y-4 p-4 bg-surface-200-700-token ${regionSource}`"
220
+ >
221
+ <slot name="source">(source)</slot>
222
+ </div>
223
+ </template>
224
+ </div>
225
+ </template>
226
+
227
+ <style scoped>
228
+ .icon {
229
+ @apply my-1 h-4 w-4 fill-current;
230
+ }
231
+ </style>
@@ -0,0 +1,14 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test } from "vitest";
3
+ import { VRail } from ".";
4
+
5
+ test("VRail", () => {
6
+ expect(VRail).toBeTruthy();
7
+ });
8
+
9
+ // TODO: add tests
10
+ test("VRail using slot", () => {
11
+ const wrapper = mount(VRail);
12
+
13
+ expect(wrapper).toBeTruthy();
14
+ });
@@ -0,0 +1,14 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test } from "vitest";
3
+ import { VRailTile } from ".";
4
+
5
+ test("VRailTile", () => {
6
+ expect(VRailTile).toBeTruthy();
7
+ });
8
+
9
+ // TODO: add tests
10
+ test("VRailTile using slot", () => {
11
+ const wrapper = mount(VRailTile);
12
+
13
+ expect(wrapper).toBeTruthy();
14
+ });
@@ -0,0 +1,14 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { expect, test } from "vitest";
3
+ import { VShell } from ".";
4
+
5
+ test("VShell", () => {
6
+ expect(VShell).toBeTruthy();
7
+ });
8
+
9
+ // TODO: add tests
10
+ test("VShell using slot", () => {
11
+ const wrapper = mount(VShell);
12
+
13
+ expect(wrapper).toBeTruthy();
14
+ });
@@ -2,5 +2,6 @@ import VDrawer from "./VDrawer.vue";
2
2
  import VRail from "./VRail.vue";
3
3
  import VRailTile from "./VRailTile.vue";
4
4
  import VShell from "./VShell.vue";
5
+ import VPreview from "./VPreview.vue";
5
6
 
6
- export { VRail, VRailTile, VShell, VDrawer };
7
+ export { VRail, VRailTile, VShell, VDrawer, VPreview };