vueless 1.0.2-beta.1 → 1.0.2-beta.10

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 (44) hide show
  1. package/README.md +3 -1
  2. package/composables/useUI.ts +1 -2
  3. package/icons/storybook/add.svg +1 -0
  4. package/icons/storybook/star.svg +1 -0
  5. package/icons/storybook/timer.svg +1 -0
  6. package/package.json +4 -4
  7. package/ui.button/tests/UButton.test.ts +1 -0
  8. package/ui.button-link/tests/ULink.test.ts +365 -0
  9. package/ui.button-toggle/UToggle.vue +3 -2
  10. package/ui.button-toggle/storybook/stories.ts +4 -18
  11. package/ui.button-toggle/tests/UToggle.test.ts +437 -0
  12. package/ui.data-table/config.ts +3 -2
  13. package/ui.form-calendar/UCalendarDayView.vue +23 -57
  14. package/ui.form-calendar/config.ts +0 -2
  15. package/ui.form-date-picker/storybook/stories.ts +1 -0
  16. package/ui.form-date-picker-range/UDatePickerRange.vue +4 -6
  17. package/ui.form-date-picker-range/UDatePickerRangeInputs.vue +6 -2
  18. package/ui.form-date-picker-range/storybook/stories.ts +1 -0
  19. package/ui.form-select/USelect.vue +8 -5
  20. package/ui.image-icon/UIcon.vue +2 -0
  21. package/ui.loader-progress/ULoaderProgress.vue +38 -2
  22. package/ui.loader-progress/types.ts +6 -0
  23. package/ui.loader-progress/useLoaderProgress.ts +18 -34
  24. package/ui.text-alert/UAlert.vue +2 -2
  25. package/ui.text-alert/tests/UAlert.test.ts +331 -0
  26. package/ui.text-badge/tests/UBadge.test.ts +322 -0
  27. package/ui.text-block/tests/UText.test.ts +148 -0
  28. package/ui.text-empty/UEmpty.vue +23 -8
  29. package/ui.text-empty/config.ts +1 -1
  30. package/ui.text-empty/tests/UEmpty.test.ts +228 -0
  31. package/ui.text-empty/types.ts +5 -0
  32. package/ui.text-file/UFile.vue +1 -1
  33. package/ui.text-file/tests/UFile.test.ts +257 -0
  34. package/ui.text-files/UFiles.vue +20 -21
  35. package/ui.text-files/storybook/stories.ts +2 -2
  36. package/ui.text-files/tests/UFiles.test.ts +325 -0
  37. package/ui.text-header/tests/UHeader.test.ts +144 -0
  38. package/ui.text-notify/tests/UNotify.test.ts +276 -0
  39. package/ui.text-number/tests/UNumber.test.ts +351 -0
  40. package/utils/node/helper.js +4 -1
  41. package/utils/node/loaderIcon.js +1 -1
  42. package/utils/node/vuelessConfig.js +4 -1
  43. package/utils/storybook.ts +1 -1
  44. package/utils/theme.ts +35 -32
package/README.md CHANGED
@@ -13,6 +13,7 @@ Vueless is simple enough for everyday use and powerful enough for advanced scena
13
13
  ### Key features
14
14
 
15
15
  - 🧩 65+ UI components (including range date picker, multi-selects, and nested table)
16
+ - 🪄 Automatic on-demand component import (as you use them)
16
17
  - 📘 Built-in Storybook support
17
18
  - 🌈 Beautiful, modern default UI theme
18
19
  - 🌗 Light and dark mode support
@@ -66,12 +67,13 @@ createApp(App).use(vueless).mount('#app');
66
67
  4. Add Vite plugins.
67
68
 
68
69
  ```javascript
69
- import { Vueless, UnpluginComponents } from "vueless/plugin-vite";
70
+ import { Vueless, TailwindCSS, UnpluginComponents } from "vueless/plugin-vite";
70
71
 
71
72
  export default defineConfig({
72
73
  plugins: [
73
74
  ...
74
75
  Vueless(),
76
+ TailwindCSS(),
75
77
  UnpluginComponents(),
76
78
  ],
77
79
  ...
@@ -2,7 +2,6 @@ import { ref, watch, getCurrentInstance, toValue, useAttrs, computed } from "vue
2
2
  import { isEqual } from "lodash-es";
3
3
 
4
4
  import { cx, cva, setColor, vuelessConfig, getMergedConfig } from "../utils/ui.ts";
5
- import { isCSR } from "../utils/helper.ts";
6
5
  import {
7
6
  CVA_CONFIG_KEY,
8
7
  SYSTEM_CONFIG_KEY,
@@ -141,7 +140,7 @@ export default function useUI<T>(
141
140
  keyConfig = config.value[configKey] as NestedComponent;
142
141
  }
143
142
 
144
- const isDev = isCSR && import.meta.env?.DEV;
143
+ const isDev = import.meta.env?.DEV;
145
144
  const isTopLevelKey = (topLevelClassKey || firstClassKey) === configKey;
146
145
 
147
146
  const extendsClasses = getExtendsClasses(configKey);
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M445.93-445.93H194.02v-68.14h251.91v-252.15h68.14v252.15h252.15v68.14H514.07v251.91h-68.14v-251.91Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="m326.37-249.79 153.64-91.89 153.64 92.9-41.28-173.94L727.5-540.33l-178.17-15.52L480-720.02l-69.33 163.41-178.17 15.28 135.22 117.38-41.35 174.16ZM224.15-107.56l67.39-291.29L65.41-594.78l298.43-25.67L480-895.3l116.16 274.85 298.43 25.67-226.13 195.93 67.63 291.29L480-262.3 224.15-107.56ZM480-474.52Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M354.5-860v-65.5h251v65.5h-251Zm92.87 452.02h65.26v-230.48h-65.26v230.48ZM479.94-70q-75.49 0-142.05-28.94-66.56-28.94-116.36-78.7-49.79-49.75-78.65-116.29-28.86-66.54-28.86-142.01 0-75.47 28.88-142.04 28.88-66.56 78.68-116.4 49.79-49.84 116.35-78.83Q404.5-802.2 480-802.2q67.48 0 127.08 22.5 59.59 22.5 105.83 62.74l53.16-53.15 46.3 46.07-53.15 53.15q36.24 40.48 61.62 97.96 25.38 57.47 25.38 136.95 0 75.52-28.96 142.11-28.95 66.59-78.78 116.31T622.04-98.92Q555.43-70 479.94-70Zm.02-68.37q124.32 0 211.11-86.62 86.78-86.62 86.78-210.95 0-124.32-86.74-211.1-86.75-86.79-211.07-86.79t-211.11 86.75q-86.78 86.74-86.78 211.06 0 124.32 86.74 210.99 86.75 86.66 211.07 86.66Zm.04-296.61Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.0.2-beta.1",
3
+ "version": "1.0.2-beta.10",
4
4
  "license": "MIT",
5
5
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
6
6
  "keywords": [
@@ -30,13 +30,13 @@
30
30
  "scripts": {
31
31
  "pre:start": "npx node .scripts/icons",
32
32
  "dev:docs": "storybook dev -p 6006 --docs --no-open",
33
- "dev": "STORYBOOK_FULL=1 storybook dev -p 6006 --no-open",
33
+ "dev": "storybook dev -p 6006 --no-open",
34
34
  "build": "storybook build --docs",
35
35
  "preview": "vite preview --host --outDir=storybook-static",
36
36
  "ts:check": "vue-tsc --build --force",
37
37
  "release:prepare": "npm run pre:start && rm -rf dist && mkdir -p dist && cp -r src/. package.json LICENSE README.md dist/ && npx node .scripts/writeLocales",
38
38
  "release:beta": "release-it --ci --npm.publish --preRelease=beta --increment=prerelease",
39
- "release:patch": "release-it patch --ci --npm.publish",
39
+ "release:patch": "release-it patch --ci --npm.publish --git.tag --github.release",
40
40
  "release:minor": "release-it minor --ci --npm.publish --git.tag --github.release",
41
41
  "release:major": "release-it major --ci --npm.publish --git.tag --github.release",
42
42
  "lint": "eslint --no-fix src/ .storybook/",
@@ -75,7 +75,7 @@
75
75
  "@vue/eslint-config-typescript": "^14.5.0",
76
76
  "@vue/test-utils": "^2.4.6",
77
77
  "@vue/tsconfig": "^0.7.0",
78
- "@vueless/storybook": "^1.0.0",
78
+ "@vueless/storybook": "^1.0.1-beta.1",
79
79
  "eslint": "^9.27.0",
80
80
  "eslint-plugin-storybook": "^0.12.0",
81
81
  "eslint-plugin-vue": "^10.1.0",
@@ -8,6 +8,7 @@ import UIcon from "../../ui.image-icon/UIcon.vue";
8
8
  import type { Props } from "../types.ts";
9
9
 
10
10
  describe("UButton.vue", () => {
11
+ // Props tests
11
12
  describe("Props", () => {
12
13
  // Variant prop
13
14
  it("applies the correct variant class", async () => {
@@ -0,0 +1,365 @@
1
+ import { mount } from "@vue/test-utils";
2
+ import { describe, it, expect } from "vitest";
3
+ import { createRouter, createWebHistory } from "vue-router";
4
+
5
+ import ULink from "../ULink.vue";
6
+
7
+ import type { Props } from "../types.ts";
8
+
9
+ // Create a mock router for testing router-link functionality
10
+ const router = createRouter({
11
+ history: createWebHistory(),
12
+ routes: [
13
+ { path: "/", name: "home", component: { template: "<div>Home</div>" } },
14
+ { path: "/about", name: "about", component: { template: "<div>About</div>" } },
15
+ ],
16
+ });
17
+
18
+ describe("ULink.vue", () => {
19
+ // Props tests
20
+ describe("Props", () => {
21
+ // Size prop
22
+ it("applies the correct size class", async () => {
23
+ const size = {
24
+ sm: "text-small",
25
+ md: "text-medium",
26
+ lg: "text-large",
27
+ };
28
+
29
+ Object.entries(size).forEach(([size, classes]) => {
30
+ const component = mount(ULink, {
31
+ props: {
32
+ size: size as Props["size"],
33
+ },
34
+ });
35
+
36
+ expect(component.attributes("class")).toContain(classes);
37
+ });
38
+ });
39
+
40
+ // Color prop
41
+ it("applies the correct color class", async () => {
42
+ const colors = [
43
+ "primary",
44
+ "secondary",
45
+ "error",
46
+ "warning",
47
+ "success",
48
+ "info",
49
+ "notice",
50
+ "neutral",
51
+ "grayscale",
52
+ "inherit",
53
+ ];
54
+
55
+ colors.forEach((color) => {
56
+ const component = mount(ULink, {
57
+ props: {
58
+ color: color as Props["color"],
59
+ },
60
+ });
61
+
62
+ expect(component.attributes("class")).toContain(color);
63
+ });
64
+ });
65
+
66
+ // Label prop
67
+ it("renders the correct label text", () => {
68
+ const label = "Link Text";
69
+
70
+ const component = mount(ULink, {
71
+ props: {
72
+ label,
73
+ },
74
+ });
75
+
76
+ expect(component.text()).toBe(label);
77
+ });
78
+
79
+ // Href prop
80
+ it("renders the correct href attribute", () => {
81
+ const href = "https://example.com";
82
+
83
+ const component = mount(ULink, {
84
+ props: {
85
+ href,
86
+ },
87
+ });
88
+
89
+ expect(component.attributes("href")).toBe(href);
90
+ });
91
+
92
+ // Type prop
93
+ it("applies the correct href prefix based on type", () => {
94
+ const types = {
95
+ phone: { href: "1234567890", expected: "tel:1234567890" },
96
+ email: { href: "test@example.com", expected: "mailto:test@example.com" },
97
+ link: { href: "https://example.com", expected: "https://example.com" },
98
+ };
99
+
100
+ Object.entries(types).forEach(([type, { href, expected }]) => {
101
+ const component = mount(ULink, {
102
+ props: {
103
+ type: type as Props["type"],
104
+ href,
105
+ },
106
+ });
107
+
108
+ expect(component.attributes("href")).toBe(expected);
109
+ });
110
+ });
111
+
112
+ // To prop
113
+ it("renders as router-link when to prop is provided", async () => {
114
+ const to = "/about";
115
+
116
+ const component = mount(ULink, {
117
+ props: {
118
+ to,
119
+ },
120
+ global: {
121
+ plugins: [router],
122
+ },
123
+ });
124
+
125
+ // Check if it's a router-link by looking for the to attribute
126
+ expect(component.findComponent({ name: "RouterLink" }).exists()).toBe(true);
127
+ });
128
+
129
+ // Target prop
130
+ it("applies the correct target attribute", () => {
131
+ const targets = ["_blank", "_self", "_parent", "_top"];
132
+
133
+ targets.forEach((target) => {
134
+ const component = mount(ULink, {
135
+ props: {
136
+ target: target as Props["target"],
137
+ href: "https://example.com",
138
+ },
139
+ });
140
+
141
+ expect(component.attributes("target")).toBe(target);
142
+ });
143
+ });
144
+
145
+ // Rel prop
146
+ it("applies the correct rel attribute", () => {
147
+ const rel = "noopener noreferrer";
148
+
149
+ const component = mount(ULink, {
150
+ props: {
151
+ rel,
152
+ href: "https://example.com",
153
+ },
154
+ });
155
+
156
+ expect(component.attributes("rel")).toBe(rel);
157
+ });
158
+
159
+ // Underlined prop
160
+ it("applies underlined class when underlined prop is true", () => {
161
+ const underlined = {
162
+ true: "underline",
163
+ false: "no-underline",
164
+ undefined: "hover:underline",
165
+ };
166
+
167
+ Object.entries(underlined).forEach(([value, expectedClass]) => {
168
+ const component = mount(ULink, {
169
+ props: {
170
+ underlined: value === "undefined" ? undefined : value === "true",
171
+ },
172
+ });
173
+
174
+ expect(component.attributes("class")).toContain(expectedClass);
175
+ });
176
+ });
177
+
178
+ // Dashed prop
179
+ it("applies dashed class when dashed prop is true", () => {
180
+ const dashed = true;
181
+ const dashedClass = "decoration-dashed";
182
+
183
+ const component = mount(ULink, {
184
+ props: {
185
+ dashed,
186
+ },
187
+ });
188
+
189
+ expect(component.attributes("class")).toContain(dashedClass);
190
+ });
191
+
192
+ // Dotted prop
193
+ it("applies dotted class when dotted prop is true", () => {
194
+ const dotted = true;
195
+ const dottedClass = "decoration-dotted";
196
+
197
+ const component = mount(ULink, {
198
+ props: {
199
+ dotted,
200
+ },
201
+ });
202
+
203
+ expect(component.attributes("class")).toContain(dottedClass);
204
+ });
205
+
206
+ // Disabled prop
207
+ it("applies disabled class when disabled prop is true", () => {
208
+ const disabled = true;
209
+
210
+ const component = mount(ULink, {
211
+ props: {
212
+ disabled,
213
+ },
214
+ });
215
+
216
+ expect(component.attributes("class")).toContain("cursor-not-allowed");
217
+ });
218
+
219
+ // Block prop
220
+ it("applies block class when block prop is true", () => {
221
+ const block = true;
222
+ const blockClass = "w-full";
223
+
224
+ const component = mount(ULink, {
225
+ props: {
226
+ block,
227
+ },
228
+ });
229
+
230
+ expect(component.attributes("class")).toContain(blockClass);
231
+ });
232
+
233
+ // DataTest prop
234
+ it("applies the correct data-test attribute", () => {
235
+ const dataTest = "test-link";
236
+
237
+ const component = mount(ULink, {
238
+ props: {
239
+ dataTest,
240
+ },
241
+ });
242
+
243
+ expect(component.attributes("data-test")).toBe(dataTest);
244
+ });
245
+ });
246
+
247
+ // Slots tests
248
+ describe("Slots", () => {
249
+ // Default slot
250
+ it("renders content from default slot", () => {
251
+ const slotContent = "Custom Content";
252
+ const label = "Link";
253
+
254
+ const component = mount(ULink, {
255
+ props: {
256
+ label,
257
+ },
258
+ slots: {
259
+ default: slotContent,
260
+ },
261
+ });
262
+
263
+ expect(component.text()).not.toContain(label);
264
+ expect(component.text()).toContain(slotContent);
265
+ });
266
+
267
+ // Default slot with router-link
268
+ it("renders content from default slot with router-link", () => {
269
+ const slotContent = "Custom Content";
270
+ const label = "Link";
271
+
272
+ const component = mount(ULink, {
273
+ props: {
274
+ label,
275
+ to: "/about",
276
+ },
277
+ slots: {
278
+ default: slotContent,
279
+ },
280
+ global: {
281
+ plugins: [router],
282
+ },
283
+ });
284
+
285
+ expect(component.text()).not.toContain(label);
286
+ expect(component.text()).toContain(slotContent);
287
+ });
288
+
289
+ // Default slot with bindings
290
+ it("provides isActive and isExactActive bindings to default slot with router-link", () => {
291
+ const component = mount(ULink, {
292
+ props: {
293
+ to: "/about",
294
+ },
295
+ slots: {
296
+ default: `
297
+ <template #default="{ isActive, isExactActive }">
298
+ <span class="active-status">{{ isActive }}</span>
299
+ <span class="exact-active-status">{{ isExactActive }}</span>
300
+ </template>
301
+ `,
302
+ },
303
+ global: {
304
+ plugins: [router],
305
+ },
306
+ });
307
+
308
+ expect(component.find(".active-status").exists()).toBe(true);
309
+ expect(component.find(".exact-active-status").exists()).toBe(true);
310
+ });
311
+ });
312
+
313
+ // Events tests
314
+ describe("Events", () => {
315
+ // Click event
316
+ it("emits click event when clicked", async () => {
317
+ const component = mount(ULink, {});
318
+
319
+ await component.trigger("click");
320
+ expect(component.emitted("click")).toBeTruthy();
321
+ });
322
+
323
+ // Mouseover event
324
+ it("emits mouseover event when hovered", async () => {
325
+ const component = mount(ULink, {});
326
+
327
+ await component.trigger("mouseover");
328
+ expect(component.emitted("mouseover")).toBeTruthy();
329
+ });
330
+
331
+ // Focus event
332
+ it("emits focus event when focused", async () => {
333
+ const component = mount(ULink, {});
334
+
335
+ await component.trigger("focus");
336
+ expect(component.emitted("focus")).toBeTruthy();
337
+ });
338
+
339
+ // Blur event
340
+ it("emits blur event when blurred", async () => {
341
+ const component = mount(ULink, {});
342
+
343
+ await component.trigger("blur");
344
+ expect(component.emitted("blur")).toBeTruthy();
345
+ });
346
+
347
+ // Keydown event
348
+ it("emits keydown event when key is pressed", async () => {
349
+ const component = mount(ULink, {});
350
+
351
+ await component.trigger("keydown");
352
+ expect(component.emitted("keydown")).toBeTruthy();
353
+ });
354
+ });
355
+
356
+ // Exposed refs tests
357
+ describe("Exposed refs", () => {
358
+ // linkRef
359
+ it("exposes linkRef", () => {
360
+ const component = mount(ULink, {});
361
+
362
+ expect(component.vm.linkRef).toBeDefined();
363
+ });
364
+ });
365
+ });
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, useTemplateRef } from "vue";
2
+ import { computed, ref, useId, useTemplateRef } from "vue";
3
3
 
4
4
  import UButton from "../ui.button/UButton.vue";
5
5
 
@@ -27,6 +27,7 @@ const emit = defineEmits([
27
27
  "update:modelValue",
28
28
  ]);
29
29
 
30
+ const elementId = props.id || useId();
30
31
  const optionsRef = useTemplateRef<HTMLDivElement>("options");
31
32
 
32
33
  const hoveredItem = ref();
@@ -106,7 +107,7 @@ const {
106
107
  </script>
107
108
 
108
109
  <template>
109
- <div ref="options" v-bind="optionsAttrs">
110
+ <div :id="elementId" ref="options" v-bind="optionsAttrs" :data-test="getDataTest()">
110
111
  <template v-for="(option, index) in options" :key="index">
111
112
  <UButton
112
113
  :label="option.label"
@@ -57,16 +57,7 @@ const DefaultTemplate: StoryFn<UToggleArgs> = (args: UToggleArgs) => ({
57
57
 
58
58
  const EnumTemplate: StoryFn<UToggleArgs> = (args: UToggleArgs, { argTypes }) => ({
59
59
  components: { UToggle, URow },
60
- setup() {
61
- const values = ref(argTypes.size?.options);
62
-
63
- return {
64
- args,
65
- values,
66
- argTypes,
67
- getArgs,
68
- };
69
- },
60
+ setup: () => ({ args, argTypes, getArgs, values: ref(argTypes.size?.options) }),
70
61
  template: `
71
62
  <URow>
72
63
  <UToggle
@@ -110,15 +101,10 @@ Square.args = {
110
101
  name: "square",
111
102
  square: true,
112
103
  options: [
113
- { value: "11", label: "star" },
114
- { value: "12", label: "add" },
115
- { value: "13", label: "timer" },
104
+ { value: "11", icon: "star" },
105
+ { value: "12", icon: "add" },
106
+ { value: "13", icon: "timer" },
116
107
  ],
117
- slotTemplate: `
118
- <template #option="{ label, index }">
119
- <UIcon :name="label" color="inherit" />
120
- </template>
121
- `,
122
108
  };
123
109
 
124
110
  export const Disabled = DefaultTemplate.bind({});