vueless 1.4.5 → 1.4.6-beta.1

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 (47) hide show
  1. package/package.json +1 -1
  2. package/ui.form-checkbox/UCheckbox.vue +16 -0
  3. package/ui.form-checkbox/tests/UCheckbox.test.ts +36 -0
  4. package/ui.form-checkbox/types.ts +1 -1
  5. package/ui.form-checkbox-group/UCheckboxGroup.vue +16 -0
  6. package/ui.form-checkbox-group/tests/UCheckboxGroup.test.ts +36 -0
  7. package/ui.form-checkbox-group/types.ts +1 -1
  8. package/ui.form-date-picker/UDatePicker.vue +16 -0
  9. package/ui.form-date-picker/tests/UDatePicker.test.ts +39 -0
  10. package/ui.form-date-picker/types.ts +1 -1
  11. package/ui.form-date-picker-range/UDatePickerRange.vue +16 -0
  12. package/ui.form-date-picker-range/tests/UDatePickerRange.test.ts +41 -0
  13. package/ui.form-date-picker-range/types.ts +1 -1
  14. package/ui.form-input/UInput.vue +8 -0
  15. package/ui.form-input/tests/UInput.test.ts +18 -0
  16. package/ui.form-input/types.ts +1 -1
  17. package/ui.form-input-file/UInputFile.vue +16 -0
  18. package/ui.form-input-file/tests/UInputFile.test.ts +36 -0
  19. package/ui.form-input-file/types.ts +1 -1
  20. package/ui.form-input-number/UInputNumber.vue +16 -0
  21. package/ui.form-input-number/tests/UInputNumber.test.ts +37 -0
  22. package/ui.form-input-number/types.ts +1 -1
  23. package/ui.form-input-password/UInputPassword.vue +16 -0
  24. package/ui.form-input-password/tests/UInputPassword.test.ts +37 -0
  25. package/ui.form-input-password/types.ts +1 -1
  26. package/ui.form-input-search/UInputSearch.vue +16 -0
  27. package/ui.form-input-search/tests/UInputSearch.test.ts +37 -0
  28. package/ui.form-input-search/types.ts +1 -1
  29. package/ui.form-label/ULabel.vue +38 -11
  30. package/ui.form-label/tests/ULabel.test.ts +33 -0
  31. package/ui.form-label/types.ts +2 -2
  32. package/ui.form-radio/URadio.vue +16 -0
  33. package/ui.form-radio/tests/URadio.test.ts +36 -0
  34. package/ui.form-radio/types.ts +1 -1
  35. package/ui.form-radio-group/URadioGroup.vue +16 -0
  36. package/ui.form-radio-group/tests/URadioGroup.test.ts +38 -0
  37. package/ui.form-radio-group/types.ts +1 -1
  38. package/ui.form-select/USelect.vue +16 -0
  39. package/ui.form-select/tests/USelect.test.ts +38 -0
  40. package/ui.form-select/types.ts +1 -1
  41. package/ui.form-switch/USwitch.vue +8 -0
  42. package/ui.form-switch/tests/USwitch.test.ts +18 -0
  43. package/ui.form-textarea/UTextarea.vue +16 -0
  44. package/ui.form-textarea/tests/UTextarea.test.ts +36 -0
  45. package/ui.form-textarea/types.ts +1 -1
  46. package/ui.text-files/UFiles.vue +8 -0
  47. package/ui.text-files/tests/UFiles.test.ts +19 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.4.5",
3
+ "version": "1.4.6-beta.1",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
@@ -165,6 +165,22 @@ const {
165
165
  <slot name="label" :label="label" />
166
166
  </template>
167
167
 
168
+ <template #description>
169
+ <!--
170
+ @slot Use this to add custom content instead of the description.
171
+ @binding {string} description
172
+ -->
173
+ <slot name="description" :description="description" />
174
+ </template>
175
+
176
+ <template #error>
177
+ <!--
178
+ @slot Use this to add custom content instead of the error message.
179
+ @binding {string | boolean} error
180
+ -->
181
+ <slot name="error" :error="error" />
182
+ </template>
183
+
168
184
  <input
169
185
  :id="elementId"
170
186
  type="checkbox"
@@ -284,6 +284,42 @@ describe("UCheckbox.vue", () => {
284
284
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
285
285
  });
286
286
 
287
+ it("Description – renders custom content from description slot", () => {
288
+ const customDescription = "Custom description content";
289
+
290
+ const component = mount(UCheckbox, {
291
+ props: {
292
+ description: "Default description",
293
+ },
294
+ slots: {
295
+ description: customDescription,
296
+ },
297
+ });
298
+
299
+ const labelComponent = component.getComponent(ULabel);
300
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
301
+
302
+ expect(descriptionElement.text()).toBe(customDescription);
303
+ });
304
+
305
+ it("Error – renders custom content from error slot", () => {
306
+ const customError = "Custom error content";
307
+
308
+ const component = mount(UCheckbox, {
309
+ props: {
310
+ error: "Default error message",
311
+ },
312
+ slots: {
313
+ error: customError,
314
+ },
315
+ });
316
+
317
+ const labelComponent = component.getComponent(ULabel);
318
+ const errorElement = labelComponent.find("[vl-child-key='error']");
319
+
320
+ expect(errorElement.text()).toBe(customError);
321
+ });
322
+
287
323
  it("Bottom – renders custom content from bottom slot", () => {
288
324
  const customBottomContent = "Custom Bottom Content";
289
325
 
@@ -53,7 +53,7 @@ export interface Props {
53
53
  /**
54
54
  * Error message.
55
55
  */
56
- error?: string;
56
+ error?: string | boolean;
57
57
 
58
58
  /**
59
59
  * Label placement.
@@ -96,6 +96,22 @@ const { getDataTest, groupLabelAttrs, groupCheckboxAttrs, listAttrs } =
96
96
  <slot name="label" :label="label" />
97
97
  </template>
98
98
 
99
+ <template #description>
100
+ <!--
101
+ @slot Use this to add custom content instead of the description.
102
+ @binding {string} description
103
+ -->
104
+ <slot name="description" :description="description" />
105
+ </template>
106
+
107
+ <template #error>
108
+ <!--
109
+ @slot Use this to add custom content instead of the error message.
110
+ @binding {string | boolean} error
111
+ -->
112
+ <slot name="error" :error="error" />
113
+ </template>
114
+
99
115
  <div ref="list" v-bind="listAttrs">
100
116
  <!-- @slot Use it to add UCheckbox directly. -->
101
117
  <slot>
@@ -289,6 +289,42 @@ describe("UCheckboxGroup.vue", () => {
289
289
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
290
290
  });
291
291
 
292
+ it("Description – renders custom content from description slot", () => {
293
+ const customDescription = "Custom description content";
294
+
295
+ const component = mount(UCheckboxGroup, {
296
+ props: {
297
+ description: "Default description",
298
+ },
299
+ slots: {
300
+ description: customDescription,
301
+ },
302
+ });
303
+
304
+ const labelComponent = component.getComponent(ULabel);
305
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
306
+
307
+ expect(descriptionElement.text()).toBe(customDescription);
308
+ });
309
+
310
+ it("Error – renders custom content from error slot", () => {
311
+ const customError = "Custom error content";
312
+
313
+ const component = mount(UCheckboxGroup, {
314
+ props: {
315
+ error: "Default error message",
316
+ },
317
+ slots: {
318
+ error: customError,
319
+ },
320
+ });
321
+
322
+ const labelComponent = component.getComponent(ULabel);
323
+ const errorElement = labelComponent.find("[vl-child-key='error']");
324
+
325
+ expect(errorElement.text()).toBe(customError);
326
+ });
327
+
292
328
  it("Default slot – renders custom UCheckbox components", () => {
293
329
  const component = mount(UCheckboxGroup, {
294
330
  props: {
@@ -39,7 +39,7 @@ export interface Props {
39
39
  /**
40
40
  * Checkbox group error message.
41
41
  */
42
- error?: string;
42
+ error?: string | boolean;
43
43
 
44
44
  /**
45
45
  * Checkbox group size.
@@ -304,6 +304,22 @@ watchEffect(() => {
304
304
  @keydown.esc="deactivate"
305
305
  @keydown="onTextInputKeyDown"
306
306
  >
307
+ <template #description>
308
+ <!--
309
+ @slot Use this to add custom content instead of the description.
310
+ @binding {string} description
311
+ -->
312
+ <slot name="description" :description="description" />
313
+ </template>
314
+
315
+ <template #error>
316
+ <!--
317
+ @slot Use this to add custom content instead of the error message.
318
+ @binding {string | boolean} error
319
+ -->
320
+ <slot name="error" :error="error" />
321
+ </template>
322
+
307
323
  <template #left="{ iconName }">
308
324
  <!--
309
325
  @slot Use it add something before the date.
@@ -4,6 +4,7 @@ import { nextTick } from "vue";
4
4
 
5
5
  import UDatePicker from "../UDatePicker.vue";
6
6
  import UInput from "../../ui.form-input/UInput.vue";
7
+ import ULabel from "../../ui.form-label/ULabel.vue";
7
8
  import UIcon from "../../ui.image-icon/UIcon.vue";
8
9
 
9
10
  import type { Props } from "../types";
@@ -476,6 +477,44 @@ describe("UDatePicker.vue", () => {
476
477
 
477
478
  expect(component.find(`.${slotClass}`).exists()).toBe(true);
478
479
  });
480
+
481
+ it("Description – renders custom content from description slot", () => {
482
+ const customDescription = "Custom description content";
483
+
484
+ const component = mount(UDatePicker, {
485
+ props: {
486
+ modelValue: new Date(),
487
+ description: "Default description",
488
+ },
489
+ slots: {
490
+ description: customDescription,
491
+ },
492
+ });
493
+
494
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
495
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
496
+
497
+ expect(descriptionElement.text()).toBe(customDescription);
498
+ });
499
+
500
+ it("Error – renders custom content from error slot", () => {
501
+ const customError = "Custom error content";
502
+
503
+ const component = mount(UDatePicker, {
504
+ props: {
505
+ modelValue: new Date(),
506
+ error: "Default error message",
507
+ },
508
+ slots: {
509
+ error: customError,
510
+ },
511
+ });
512
+
513
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
514
+ const errorElement = labelComponent.find("[vl-child-key='error']");
515
+
516
+ expect(errorElement.text()).toBe(customError);
517
+ });
479
518
  });
480
519
 
481
520
  describe("Exposed Properties", () => {
@@ -34,7 +34,7 @@ export interface Props<TModelValue> {
34
34
  /**
35
35
  * Datepicker error message.
36
36
  */
37
- error?: string;
37
+ error?: string | boolean;
38
38
 
39
39
  /**
40
40
  * Make datepicker disabled.
@@ -639,6 +639,22 @@ watchEffect(() => {
639
639
  @focus="activate"
640
640
  @keydown.esc="deactivate"
641
641
  >
642
+ <template #description>
643
+ <!--
644
+ @slot Use this to add custom content instead of the description.
645
+ @binding {string} description
646
+ -->
647
+ <slot name="description" :description="description" />
648
+ </template>
649
+
650
+ <template #error>
651
+ <!--
652
+ @slot Use this to add custom content instead of the error message.
653
+ @binding {string | boolean} error
654
+ -->
655
+ <slot name="error" :error="error" />
656
+ </template>
657
+
642
658
  <template #left="{ iconName }">
643
659
  <!--
644
660
  @slot Use it to add something before the date.
@@ -3,6 +3,7 @@ import { describe, it, expect } from "vitest";
3
3
 
4
4
  import UDatePickerRange from "../UDatePickerRange.vue";
5
5
  import UInput from "../../ui.form-input/UInput.vue";
6
+ import ULabel from "../../ui.form-label/ULabel.vue";
6
7
  import UButton from "../../ui.button/UButton.vue";
7
8
  import UDatePickerRangePeriodMenu from "../UDatePickerRangePeriodMenu.vue";
8
9
 
@@ -583,6 +584,46 @@ describe("UDatePickerRange.vue", () => {
583
584
 
584
585
  expect(component.text()).toContain(`Icon: ${rightIcon}`);
585
586
  });
587
+
588
+ it("Description – renders custom content from description slot", () => {
589
+ const customDescription = "Custom description content";
590
+
591
+ const component = mount(UDatePickerRange, {
592
+ props: {
593
+ variant: "input",
594
+ modelValue: { from: null, to: null },
595
+ description: "Default description",
596
+ },
597
+ slots: {
598
+ description: customDescription,
599
+ },
600
+ });
601
+
602
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
603
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
604
+
605
+ expect(descriptionElement.text()).toBe(customDescription);
606
+ });
607
+
608
+ it("Error – renders custom content from error slot", () => {
609
+ const customError = "Custom error content";
610
+
611
+ const component = mount(UDatePickerRange, {
612
+ props: {
613
+ variant: "input",
614
+ modelValue: { from: null, to: null },
615
+ error: "Default error message",
616
+ },
617
+ slots: {
618
+ error: customError,
619
+ },
620
+ });
621
+
622
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
623
+ const errorElement = labelComponent.find("[vl-child-key='error']");
624
+
625
+ expect(errorElement.text()).toBe(customError);
626
+ });
586
627
  });
587
628
 
588
629
  describe("Events", () => {
@@ -110,7 +110,7 @@ export interface Props<TModelValue> {
110
110
  /**
111
111
  * Datepicker error message.
112
112
  */
113
- error?: string;
113
+ error?: string | boolean;
114
114
 
115
115
  /**
116
116
  * Make datepicker disabled.
@@ -298,6 +298,14 @@ const {
298
298
  <slot name="description" :description="description" />
299
299
  </template>
300
300
 
301
+ <template #error>
302
+ <!--
303
+ @slot Use this to add custom content instead of the error message.
304
+ @binding {string | boolean} error
305
+ -->
306
+ <slot name="error" :error="error" />
307
+ </template>
308
+
301
309
  <div ref="wrapper" v-bind="wrapperAttrs">
302
310
  <span
303
311
  v-if="hasSlotContent($slots['left'], { iconName: leftIcon }) || leftIcon"
@@ -359,6 +359,24 @@ describe("UInput.vue", () => {
359
359
  expect(descriptionElement.text()).toBe(customDescription);
360
360
  });
361
361
 
362
+ it("Error – renders custom content from error slot", () => {
363
+ const customError = "Custom error content";
364
+
365
+ const component = mount(UInput, {
366
+ props: {
367
+ error: "Default error message",
368
+ },
369
+ slots: {
370
+ error: customError,
371
+ },
372
+ });
373
+
374
+ const labelComponent = component.getComponent(ULabel);
375
+ const errorElement = labelComponent.find("[vl-child-key='error']");
376
+
377
+ expect(errorElement.text()).toBe(customError);
378
+ });
379
+
362
380
  it("Left – renders custom content from left slot", () => {
363
381
  const slotText = "Custom Left Content";
364
382
  const slotClass = "custom-left";
@@ -33,7 +33,7 @@ export interface Props {
33
33
  /**
34
34
  * Error message.
35
35
  */
36
- error?: string;
36
+ error?: string | boolean;
37
37
 
38
38
  /**
39
39
  * Input size.
@@ -313,6 +313,22 @@ const {
313
313
  <slot name="label" :label="label" />
314
314
  </template>
315
315
 
316
+ <template #description>
317
+ <!--
318
+ @slot Use this to add custom content instead of the description.
319
+ @binding {string} description
320
+ -->
321
+ <slot name="description" :description="description" />
322
+ </template>
323
+
324
+ <template #error>
325
+ <!--
326
+ @slot Use this to add custom content instead of the error message.
327
+ @binding {string | boolean} error
328
+ -->
329
+ <slot name="error" :error="currentError" />
330
+ </template>
331
+
316
332
  <div ref="dropZone" :ondrop="onDrop" v-bind="dropzoneAttrs">
317
333
  <UText v-if="hasSlotContent($slots['top'])" :size="size" v-bind="descriptionTopAttrs">
318
334
  <!-- @slot Use it to add something above the component content. -->
@@ -292,6 +292,42 @@ describe("UInputFile.vue", () => {
292
292
  expect(labelElement.text()).toBe(`Custom ${defaultLabel}`);
293
293
  });
294
294
 
295
+ it("Description – renders custom content from description slot", () => {
296
+ const customDescription = "Custom description content";
297
+
298
+ const component = mount(UInputFile, {
299
+ props: {
300
+ description: "Default description",
301
+ },
302
+ slots: {
303
+ description: customDescription,
304
+ },
305
+ });
306
+
307
+ const labelComponent = component.getComponent(ULabel);
308
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
309
+
310
+ expect(descriptionElement.text()).toBe(customDescription);
311
+ });
312
+
313
+ it("Error – renders custom content from error slot", () => {
314
+ const customError = "Custom error content";
315
+
316
+ const component = mount(UInputFile, {
317
+ props: {
318
+ error: "Default error message",
319
+ },
320
+ slots: {
321
+ error: customError,
322
+ },
323
+ });
324
+
325
+ const labelComponent = component.getComponent(ULabel);
326
+ const errorElement = labelComponent.find("[vl-child-key='error']");
327
+
328
+ expect(errorElement.text()).toBe(customError);
329
+ });
330
+
295
331
  it("Top – renders custom content from top slot", () => {
296
332
  const testClass = "custom-top";
297
333
 
@@ -23,7 +23,7 @@ export interface Props {
23
23
  /**
24
24
  * Error message.
25
25
  */
26
- error?: string;
26
+ error?: string | boolean;
27
27
 
28
28
  /**
29
29
  * Label placement.
@@ -179,6 +179,22 @@ const { getDataTest, numberInputAttrs } = useUI<Config>(defaultConfig);
179
179
  @blur="onBlur"
180
180
  @input="onInput"
181
181
  >
182
+ <template #description>
183
+ <!--
184
+ @slot Use this to add custom content instead of the description.
185
+ @binding {string} description
186
+ -->
187
+ <slot name="description" :description="description" />
188
+ </template>
189
+
190
+ <template #error>
191
+ <!--
192
+ @slot Use this to add custom content instead of the error message.
193
+ @binding {string | boolean} error
194
+ -->
195
+ <slot name="error" :error="error" />
196
+ </template>
197
+
182
198
  <template #left>
183
199
  <!--
184
200
  @slot Use it to add something left.
@@ -3,6 +3,7 @@ import { describe, it, expect } from "vitest";
3
3
 
4
4
  import UInputNumber from "../UInputNumber.vue";
5
5
  import UInput from "../../ui.form-input/UInput.vue";
6
+ import ULabel from "../../ui.form-label/ULabel.vue";
6
7
  import UIcon from "../../ui.image-icon/UIcon.vue";
7
8
 
8
9
  import type { Props } from "../types";
@@ -412,6 +413,42 @@ describe("UInputNumber.vue", () => {
412
413
  expect(component.find(`.${slotClass}`).exists()).toBe(true);
413
414
  expect(component.findComponent(UIcon).exists()).toBe(false);
414
415
  });
416
+
417
+ it("Description – renders custom content from description slot", () => {
418
+ const customDescription = "Custom description content";
419
+
420
+ const component = mount(UInputNumber, {
421
+ props: {
422
+ description: "Default description",
423
+ },
424
+ slots: {
425
+ description: customDescription,
426
+ },
427
+ });
428
+
429
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
430
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
431
+
432
+ expect(descriptionElement.text()).toBe(customDescription);
433
+ });
434
+
435
+ it("Error – renders custom content from error slot", () => {
436
+ const customError = "Custom error content";
437
+
438
+ const component = mount(UInputNumber, {
439
+ props: {
440
+ error: "Default error message",
441
+ },
442
+ slots: {
443
+ error: customError,
444
+ },
445
+ });
446
+
447
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
448
+ const errorElement = labelComponent.find("[vl-child-key='error']");
449
+
450
+ expect(errorElement.text()).toBe(customError);
451
+ });
415
452
  });
416
453
 
417
454
  describe("Exposed properties", () => {
@@ -52,7 +52,7 @@ export interface Props {
52
52
  /**
53
53
  * Error message.
54
54
  */
55
- error?: string;
55
+ error?: string | boolean;
56
56
 
57
57
  /**
58
58
  * Input size.
@@ -125,6 +125,22 @@ const { getDataTest, config, passwordInputAttrs, passwordIconAttrs, passwordIcon
125
125
  <slot name="label" :label="label" />
126
126
  </template>
127
127
 
128
+ <template #description>
129
+ <!--
130
+ @slot Use this to add custom content instead of the description.
131
+ @binding {string} description
132
+ -->
133
+ <slot name="description" :description="description" />
134
+ </template>
135
+
136
+ <template #error>
137
+ <!--
138
+ @slot Use this to add custom content instead of the error message.
139
+ @binding {string | boolean} error
140
+ -->
141
+ <slot name="error" :error="error" />
142
+ </template>
143
+
128
144
  <template #right>
129
145
  <div v-bind="passwordIconWrapperAttrs" @click="onClickShowPassword">
130
146
  <!--
@@ -3,6 +3,7 @@ import { describe, it, expect } from "vitest";
3
3
 
4
4
  import UInputPassword from "../UInputPassword.vue";
5
5
  import UInput from "../../ui.form-input/UInput.vue";
6
+ import ULabel from "../../ui.form-label/ULabel.vue";
6
7
  import UIcon from "../../ui.image-icon/UIcon.vue";
7
8
 
8
9
  import type { Props } from "../types";
@@ -299,5 +300,41 @@ describe("UInputPassword.vue", () => {
299
300
 
300
301
  expect(component.html()).toContain(`Icon: ${visibilityIcon}`);
301
302
  });
303
+
304
+ it("Description – renders custom content from description slot", () => {
305
+ const customDescription = "Custom description content";
306
+
307
+ const component = mount(UInputPassword, {
308
+ props: {
309
+ description: "Default description",
310
+ },
311
+ slots: {
312
+ description: customDescription,
313
+ },
314
+ });
315
+
316
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
317
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
318
+
319
+ expect(descriptionElement.text()).toBe(customDescription);
320
+ });
321
+
322
+ it("Error – renders custom content from error slot", () => {
323
+ const customError = "Custom error content";
324
+
325
+ const component = mount(UInputPassword, {
326
+ props: {
327
+ error: "Default error message",
328
+ },
329
+ slots: {
330
+ error: customError,
331
+ },
332
+ });
333
+
334
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
335
+ const errorElement = labelComponent.find("[vl-child-key='error']");
336
+
337
+ expect(errorElement.text()).toBe(customError);
338
+ });
302
339
  });
303
340
  });
@@ -33,7 +33,7 @@ export interface Props {
33
33
  /**
34
34
  * Error message.
35
35
  */
36
- error?: string;
36
+ error?: string | boolean;
37
37
 
38
38
  /**
39
39
  * Input size.
@@ -144,6 +144,22 @@ const {
144
144
  @update:model-value="onUpdateValue"
145
145
  @keyup.enter="onKeyupEnter"
146
146
  >
147
+ <template #description>
148
+ <!--
149
+ @slot Use this to add custom content instead of the description.
150
+ @binding {string} description
151
+ -->
152
+ <slot name="description" :description="description" />
153
+ </template>
154
+
155
+ <template #error>
156
+ <!--
157
+ @slot Use this to add custom content instead of the error message.
158
+ @binding {string | boolean} error
159
+ -->
160
+ <slot name="error" :error="error" />
161
+ </template>
162
+
147
163
  <template #left>
148
164
  <!-- @slot Use it to add something before the text. -->
149
165
  <slot name="left" />
@@ -2,6 +2,7 @@ import { flushPromises, mount } from "@vue/test-utils";
2
2
  import { describe, it, expect, vi } from "vitest";
3
3
 
4
4
  import UInputSearch from "../UInputSearch.vue";
5
+ import ULabel from "../../ui.form-label/ULabel.vue";
5
6
  import UInput from "../../ui.form-input/UInput.vue";
6
7
  import UIcon from "../../ui.image-icon/UIcon.vue";
7
8
  import UButton from "../../ui.button/UButton.vue";
@@ -301,6 +302,42 @@ describe("UInputSearch.vue", () => {
301
302
 
302
303
  expect(component.getComponent(UButton).props("label")).toBe(searchButtonLabel);
303
304
  });
305
+
306
+ it("Description – renders custom content from description slot", () => {
307
+ const customDescription = "Custom description content";
308
+
309
+ const component = mount(UInputSearch, {
310
+ props: {
311
+ description: "Default description",
312
+ },
313
+ slots: {
314
+ description: customDescription,
315
+ },
316
+ });
317
+
318
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
319
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
320
+
321
+ expect(descriptionElement.text()).toBe(customDescription);
322
+ });
323
+
324
+ it("Error – renders custom content from error slot", () => {
325
+ const customError = "Custom error content";
326
+
327
+ const component = mount(UInputSearch, {
328
+ props: {
329
+ error: "Default error message",
330
+ },
331
+ slots: {
332
+ error: customError,
333
+ },
334
+ });
335
+
336
+ const labelComponent = component.getComponent(UInput).getComponent(ULabel);
337
+ const errorElement = labelComponent.find("[vl-child-key='error']");
338
+
339
+ expect(errorElement.text()).toBe(customError);
340
+ });
304
341
  });
305
342
 
306
343
  describe("Events", () => {
@@ -38,7 +38,7 @@ export interface Props {
38
38
  /**
39
39
  * Error message.
40
40
  */
41
- error?: string;
41
+ error?: string | boolean;
42
42
 
43
43
  /**
44
44
  * Minimum character length for search.
@@ -51,10 +51,23 @@ const wrapperElement = computed(() => {
51
51
  return wrapperRef.value;
52
52
  });
53
53
 
54
- const isShownError = computed(() => {
54
+ const hasErrorState = computed(() => {
55
55
  return Boolean(props.error) && !props.disabled;
56
56
  });
57
57
 
58
+ const errorFallbackText = computed(() => {
59
+ return typeof props.error === "string" ? props.error : "";
60
+ });
61
+
62
+ const showErrorBlock = computed(() => {
63
+ if (!hasErrorState.value) return false;
64
+
65
+ return (
66
+ hasSlotContent(slots["error"], { error: props.error }) ||
67
+ (typeof props.error !== "boolean" && Boolean(props.error))
68
+ );
69
+ });
70
+
58
71
  function onClick(event: MouseEvent) {
59
72
  emit("click", event);
60
73
  }
@@ -78,7 +91,7 @@ defineExpose({
78
91
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
79
92
  */
80
93
  const mutatedProps = computed(() => ({
81
- error: Boolean(props.error) && !props.disabled,
94
+ error: hasErrorState.value,
82
95
  label: Boolean(props.label) || hasSlotContent(slots["label"], { label: props.label }),
83
96
  description:
84
97
  Boolean(props.description) ||
@@ -108,6 +121,7 @@ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs, e
108
121
  label ||
109
122
  hasSlotContent(slots['label'], { label }) ||
110
123
  error ||
124
+ hasSlotContent(slots['error'], { error }) ||
111
125
  description ||
112
126
  hasSlotContent(slots['description'], { description })
113
127
  "
@@ -131,16 +145,19 @@ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs, e
131
145
  </slot>
132
146
  </component>
133
147
 
134
- <div
135
- v-if="isShownError"
136
- v-bind="errorAttrs"
137
- :data-test="getDataTest('error')"
138
- v-text="error"
139
- />
148
+ <div v-if="showErrorBlock" v-bind="errorAttrs" :data-test="getDataTest('error')">
149
+ <!--
150
+ @slot Use this to add custom content instead of the error message.
151
+ @binding {string | boolean} error
152
+ -->
153
+ <slot name="error" :error="error">
154
+ {{ errorFallbackText }}
155
+ </slot>
156
+ </div>
140
157
 
141
158
  <div
142
159
  v-if="
143
- (description || hasSlotContent(slots['description'], { description })) && !isShownError
160
+ (description || hasSlotContent(slots['description'], { description })) && !hasErrorState
144
161
  "
145
162
  v-bind="descriptionAttrs"
146
163
  :data-test="getDataTest('description')"
@@ -184,10 +201,20 @@ const { getDataTest, wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs, e
184
201
  <slot />
185
202
  </div>
186
203
 
187
- <div v-if="isShownError" v-bind="errorAttrs" :data-test="getDataTest('error')" v-text="error" />
204
+ <div v-if="showErrorBlock" v-bind="errorAttrs" :data-test="getDataTest('error')">
205
+ <!--
206
+ @slot Use this to add custom content instead of the error message.
207
+ @binding {string | boolean} error
208
+ -->
209
+ <slot name="error" :error="error">
210
+ {{ errorFallbackText }}
211
+ </slot>
212
+ </div>
188
213
 
189
214
  <div
190
- v-if="(description || hasSlotContent(slots['description'], { description })) && !isShownError"
215
+ v-if="
216
+ (description || hasSlotContent(slots['description'], { description })) && !hasErrorState
217
+ "
191
218
  v-bind="descriptionAttrs"
192
219
  :data-test="getDataTest('description')"
193
220
  >
@@ -97,6 +97,39 @@ describe("ULabel.vue", () => {
97
97
  expect(errorElement.text()).toBe(error);
98
98
  });
99
99
 
100
+ it("Error – boolean error applies error styling without rendering error text", () => {
101
+ const errorClasses = "text-error";
102
+
103
+ const component = mount(ULabel, {
104
+ props: {
105
+ error: true,
106
+ label: "Label",
107
+ },
108
+ });
109
+
110
+ expect(component.find("[vl-key='label']").attributes("class")).toContain(errorClasses);
111
+ expect(component.find("[vl-key='error']").exists()).toBe(false);
112
+ });
113
+
114
+ it("Error – renders content from error slot", () => {
115
+ const slotContent = "Custom error from slot";
116
+ const defaultError = "Default error";
117
+
118
+ const component = mount(ULabel, {
119
+ props: {
120
+ error: defaultError,
121
+ },
122
+ slots: {
123
+ error: slotContent,
124
+ },
125
+ });
126
+
127
+ const errorElement = component.find("[vl-key='error']");
128
+
129
+ expect(errorElement.text()).toBe(slotContent);
130
+ expect(errorElement.text()).not.toContain(defaultError);
131
+ });
132
+
100
133
  it("Align – applies correct classes for align prop", () => {
101
134
  const alignCases = {
102
135
  top: "flex-col",
@@ -26,9 +26,9 @@ export interface Props {
26
26
  description?: string;
27
27
 
28
28
  /**
29
- * Label error message.
29
+ * Label error message, or `true` for error styling without showing message text.
30
30
  */
31
- error?: string;
31
+ error?: string | boolean;
32
32
 
33
33
  /**
34
34
  * Label align.
@@ -124,6 +124,22 @@ const { getDataTest, radioLabelAttrs, radioAttrs } = useUI<Config>(defaultConfig
124
124
  <slot name="label" :label="label" />
125
125
  </template>
126
126
 
127
+ <template #description>
128
+ <!--
129
+ @slot Use this to add custom content instead of the description.
130
+ @binding {string} description
131
+ -->
132
+ <slot name="description" :description="description" />
133
+ </template>
134
+
135
+ <template #error>
136
+ <!--
137
+ @slot Use this to add custom content instead of the error message.
138
+ @binding {string | boolean} error
139
+ -->
140
+ <slot name="error" :error="error" />
141
+ </template>
142
+
127
143
  <input
128
144
  :id="elementId"
129
145
  type="radio"
@@ -206,6 +206,42 @@ describe("URadio.vue", () => {
206
206
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
207
207
  });
208
208
 
209
+ it("Description – renders custom content from description slot", () => {
210
+ const customDescription = "Custom description content";
211
+
212
+ const component = mount(URadio, {
213
+ props: {
214
+ description: "Default description",
215
+ },
216
+ slots: {
217
+ description: customDescription,
218
+ },
219
+ });
220
+
221
+ const labelComponent = component.getComponent(ULabel);
222
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
223
+
224
+ expect(descriptionElement.text()).toBe(customDescription);
225
+ });
226
+
227
+ it("Error – renders custom content from error slot", () => {
228
+ const customError = "Custom error content";
229
+
230
+ const component = mount(URadio, {
231
+ props: {
232
+ error: "Default error message",
233
+ },
234
+ slots: {
235
+ error: customError,
236
+ },
237
+ });
238
+
239
+ const labelComponent = component.getComponent(ULabel);
240
+ const errorElement = labelComponent.find("[vl-child-key='error']");
241
+
242
+ expect(errorElement.text()).toBe(customError);
243
+ });
244
+
209
245
  it("Bottom – renders custom content from bottom slot", () => {
210
246
  const customBottomContent = "Custom Bottom Content";
211
247
 
@@ -35,7 +35,7 @@ export interface Props {
35
35
  /**
36
36
  * Error message.
37
37
  */
38
- error?: string;
38
+ error?: string | boolean;
39
39
 
40
40
  /**
41
41
  * Radio name.
@@ -79,6 +79,22 @@ const { getDataTest, groupLabelAttrs, listAttrs, groupRadioAttrs } = useUI<Confi
79
79
  <slot name="label" :label="label" />
80
80
  </template>
81
81
 
82
+ <template #description>
83
+ <!--
84
+ @slot Use this to add custom content instead of the description.
85
+ @binding {string} description
86
+ -->
87
+ <slot name="description" :description="description" />
88
+ </template>
89
+
90
+ <template #error>
91
+ <!--
92
+ @slot Use this to add custom content instead of the error message.
93
+ @binding {string | boolean} error
94
+ -->
95
+ <slot name="error" :error="error" />
96
+ </template>
97
+
82
98
  <div ref="list" v-bind="listAttrs">
83
99
  <!-- @slot Use it to add URadio directly. -->
84
100
  <slot>
@@ -303,6 +303,44 @@ describe("URadioGroup.vue", () => {
303
303
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
304
304
  });
305
305
 
306
+ it("Description – renders custom content from description slot", () => {
307
+ const customDescription = "Custom description content";
308
+
309
+ const component = mount(URadioGroup, {
310
+ props: {
311
+ description: "Default description",
312
+ name: defaultName,
313
+ },
314
+ slots: {
315
+ description: customDescription,
316
+ },
317
+ });
318
+
319
+ const labelComponent = component.getComponent(ULabel);
320
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
321
+
322
+ expect(descriptionElement.text()).toBe(customDescription);
323
+ });
324
+
325
+ it("Error – renders custom content from error slot", () => {
326
+ const customError = "Custom error content";
327
+
328
+ const component = mount(URadioGroup, {
329
+ props: {
330
+ error: "Default error message",
331
+ name: defaultName,
332
+ },
333
+ slots: {
334
+ error: customError,
335
+ },
336
+ });
337
+
338
+ const labelComponent = component.getComponent(ULabel);
339
+ const errorElement = labelComponent.find("[vl-child-key='error']");
340
+
341
+ expect(errorElement.text()).toBe(customError);
342
+ });
343
+
306
344
  it("Default slot – renders custom URadio components", () => {
307
345
  const component = mount(URadioGroup, {
308
346
  props: {
@@ -52,7 +52,7 @@ export interface Props {
52
52
  /**
53
53
  * Radio group error message.
54
54
  */
55
- error?: string;
55
+ error?: string | boolean;
56
56
 
57
57
  /**
58
58
  * Radio size.
@@ -538,6 +538,22 @@ const {
538
538
  </div>
539
539
  </template>
540
540
 
541
+ <template #description>
542
+ <!--
543
+ @slot Use this to add custom content instead of the description.
544
+ @binding {string} description
545
+ -->
546
+ <slot name="description" :description="description" />
547
+ </template>
548
+
549
+ <template #error>
550
+ <!--
551
+ @slot Use this to add custom content instead of the error message.
552
+ @binding {string | boolean} error
553
+ -->
554
+ <slot name="error" :error="error" />
555
+ </template>
556
+
541
557
  <div
542
558
  ref="wrapper"
543
559
  :tabindex="searchable || disabled ? -1 : 0"
@@ -587,6 +587,44 @@ describe("USelect.vue", () => {
587
587
  expect(component.text()).toContain(`Modified ${defaultLabel}`);
588
588
  });
589
589
 
590
+ it("Description – renders custom content from description slot", () => {
591
+ const customDescription = "Custom description content";
592
+
593
+ const component = mount(USelect, {
594
+ props: {
595
+ description: "Default description",
596
+ options: defaultOptions,
597
+ },
598
+ slots: {
599
+ description: customDescription,
600
+ },
601
+ });
602
+
603
+ const labelComponent = component.getComponent(ULabel);
604
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
605
+
606
+ expect(descriptionElement.text()).toBe(customDescription);
607
+ });
608
+
609
+ it("Error – renders custom content from error slot", () => {
610
+ const customError = "Custom error content";
611
+
612
+ const component = mount(USelect, {
613
+ props: {
614
+ error: "Default error message",
615
+ options: defaultOptions,
616
+ },
617
+ slots: {
618
+ error: customError,
619
+ },
620
+ });
621
+
622
+ const labelComponent = component.getComponent(ULabel);
623
+ const errorElement = labelComponent.find("[vl-child-key='error']");
624
+
625
+ expect(errorElement.text()).toBe(customError);
626
+ });
627
+
590
628
  it("Left – renders custom content from left slot", () => {
591
629
  const slotContent = "Left Slot Content";
592
630
 
@@ -39,7 +39,7 @@ export interface Props {
39
39
  /**
40
40
  * Select error message.
41
41
  */
42
- error?: string;
42
+ error?: string | boolean;
43
43
 
44
44
  /**
45
45
  * Select size.
@@ -129,6 +129,14 @@ const {
129
129
  <slot name="label" :label="label" />
130
130
  </template>
131
131
 
132
+ <template #description>
133
+ <!--
134
+ @slot Use this to add custom content instead of the description.
135
+ @binding {string} description
136
+ -->
137
+ <slot name="description" :description="description" />
138
+ </template>
139
+
132
140
  <div
133
141
  ref="wrapper"
134
142
  tabindex="0"
@@ -229,6 +229,24 @@ describe("USwitch.vue", () => {
229
229
 
230
230
  expect(component.find("label").text()).toBe(`Modified ${defaultLabel}`);
231
231
  });
232
+
233
+ it("Description – renders custom content from description slot", () => {
234
+ const customDescription = "Custom description content";
235
+
236
+ const component = mount(USwitch, {
237
+ props: {
238
+ description: "Default description",
239
+ },
240
+ slots: {
241
+ description: customDescription,
242
+ },
243
+ });
244
+
245
+ const labelComponent = component.getComponent(ULabel);
246
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
247
+
248
+ expect(descriptionElement.text()).toBe(customDescription);
249
+ });
232
250
  });
233
251
 
234
252
  describe("Exposed properties", () => {
@@ -250,6 +250,22 @@ const {
250
250
  <slot name="label" :label="label" />
251
251
  </template>
252
252
 
253
+ <template #description>
254
+ <!--
255
+ @slot Use this to add custom content instead of the description.
256
+ @binding {string} description
257
+ -->
258
+ <slot name="description" :description="description" />
259
+ </template>
260
+
261
+ <template #error>
262
+ <!--
263
+ @slot Use this to add custom content instead of the error message.
264
+ @binding {string | boolean} error
265
+ -->
266
+ <slot name="error" :error="error" />
267
+ </template>
268
+
253
269
  <div ref="wrapper" v-bind="wrapperAttrs">
254
270
  <span
255
271
  v-if="hasSlotContent($slots['left'])"
@@ -430,6 +430,42 @@ describe("UTextarea.vue", () => {
430
430
  expect(labelElement.text()).toBe(`Modified ${defaultLabel}`);
431
431
  });
432
432
 
433
+ it("Description – renders custom content from description slot", () => {
434
+ const customDescription = "Custom description content";
435
+
436
+ const component = mount(UTextarea, {
437
+ props: {
438
+ description: "Default description",
439
+ },
440
+ slots: {
441
+ description: customDescription,
442
+ },
443
+ });
444
+
445
+ const labelComponent = component.getComponent(ULabel);
446
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
447
+
448
+ expect(descriptionElement.text()).toBe(customDescription);
449
+ });
450
+
451
+ it("Error – renders custom content from error slot", () => {
452
+ const customError = "Custom error content";
453
+
454
+ const component = mount(UTextarea, {
455
+ props: {
456
+ error: "Default error message",
457
+ },
458
+ slots: {
459
+ error: customError,
460
+ },
461
+ });
462
+
463
+ const labelComponent = component.getComponent(ULabel);
464
+ const errorElement = labelComponent.find("[vl-child-key='error']");
465
+
466
+ expect(errorElement.text()).toBe(customError);
467
+ });
468
+
433
469
  it("Left – renders custom content from left slot", () => {
434
470
  const slotText = "Custom Left Content";
435
471
  const testClass = "custom-left";
@@ -73,7 +73,7 @@ export interface Props {
73
73
  /**
74
74
  * Set error message.
75
75
  */
76
- error?: string;
76
+ error?: string | boolean;
77
77
 
78
78
  /**
79
79
  * Set number of visible rows.
@@ -81,6 +81,14 @@ const { getDataTest, filesLabelAttrs, itemsAttrs, itemAttrs } = useUI<Config>(de
81
81
  <slot name="label" :label="label" />
82
82
  </template>
83
83
 
84
+ <template #description>
85
+ <!--
86
+ @slot Use this to add custom content instead of the description.
87
+ @binding {string} description
88
+ -->
89
+ <slot name="description" :description="description" />
90
+ </template>
91
+
84
92
  <div ref="items" v-bind="itemsAttrs">
85
93
  <!-- @slot Use it to add UFile. -->
86
94
  <slot>
@@ -173,6 +173,25 @@ describe("UFiles.vue", () => {
173
173
  expect(component.find(`.${slotClass}`).text()).toBe(slotText);
174
174
  });
175
175
 
176
+ it("Description – renders custom content from description slot", () => {
177
+ const customDescription = "Custom description content";
178
+
179
+ const component = mount(UFiles, {
180
+ props: {
181
+ fileList: [],
182
+ description: "Default description",
183
+ },
184
+ slots: {
185
+ description: customDescription,
186
+ },
187
+ });
188
+
189
+ const labelComponent = component.getComponent(ULabel);
190
+ const descriptionElement = labelComponent.find("[vl-child-key='description']");
191
+
192
+ expect(descriptionElement.text()).toBe(customDescription);
193
+ });
194
+
176
195
  it("Before – renders content from before-file slot", () => {
177
196
  const slotText = "Before";
178
197
  const slotClass = "before-content";